From e743a5a16e78896e627698ebc584d67d2dfc3574 Mon Sep 17 00:00:00 2001 From: Yigit Colakoglu Date: Tue, 6 Apr 2021 13:53:35 +0300 Subject: [PATCH] Further suckless support --- .../suckless@surf@config.h | 28 +- .../suckless@surf@config.h.blob | Bin 4161 -> 3759 bytes arch-setup/packages.rice | 11 +- bash_logout | 1 + config/X11/xinitrc | 4 +- config/surf/styles/{default.css => wiki.css} | 0 config/zsh/profile | 18 - local/bin/surf_linkselect.sh | 4 +- notes/firefox.md | 4 + profile | 51 +- suckless/surf/config.h | 28 +- suckless/sxiv/.gitignore | 57 ++ suckless/sxiv/LICENSE | 339 +++++++ suckless/sxiv/Makefile | 88 ++ suckless/sxiv/TODO | 5 + suckless/sxiv/autoreload_inotify.c | 112 +++ suckless/sxiv/autoreload_nop.c | 42 + suckless/sxiv/commands.c | 455 +++++++++ suckless/sxiv/commands.lst | 37 + suckless/sxiv/config.def.h | 152 +++ suckless/sxiv/config.h | 152 +++ suckless/sxiv/exec/image-info | 20 + suckless/sxiv/exec/key-handler | 35 + suckless/sxiv/icon/128x128.png | Bin 0 -> 542 bytes suckless/sxiv/icon/16x16.png | Bin 0 -> 178 bytes suckless/sxiv/icon/32x32.png | Bin 0 -> 221 bytes suckless/sxiv/icon/48x48.png | Bin 0 -> 282 bytes suckless/sxiv/icon/64x64.png | Bin 0 -> 319 bytes suckless/sxiv/icon/Makefile | 12 + suckless/sxiv/icon/dat2h.awk | 35 + suckless/sxiv/icon/data.h | 247 +++++ suckless/sxiv/image.c | 797 +++++++++++++++ suckless/sxiv/main.c | 951 ++++++++++++++++++ suckless/sxiv/options.c | 180 ++++ suckless/sxiv/sxiv | Bin 0 -> 106944 bytes suckless/sxiv/sxiv.1 | 448 +++++++++ suckless/sxiv/sxiv.desktop | 8 + suckless/sxiv/sxiv.h | 448 +++++++++ suckless/sxiv/thumbs.c | 594 +++++++++++ suckless/sxiv/utf8.h | 68 ++ suckless/sxiv/util.c | 213 ++++ suckless/sxiv/version.h | 1 + suckless/sxiv/window.c | 476 +++++++++ suckless/xbanish/.gitignore | 57 ++ suckless/xbanish/LICENSE | 14 + suckless/xbanish/Makefile | 39 + suckless/xbanish/xbanish | Bin 0 -> 27016 bytes suckless/xbanish/xbanish.1 | 60 ++ suckless/xbanish/xbanish.c | 487 +++++++++ 49 files changed, 6696 insertions(+), 82 deletions(-) rename config/surf/styles/{default.css => wiki.css} (100%) delete mode 100755 config/zsh/profile create mode 100644 suckless/sxiv/.gitignore create mode 100644 suckless/sxiv/LICENSE create mode 100644 suckless/sxiv/Makefile create mode 100644 suckless/sxiv/TODO create mode 100644 suckless/sxiv/autoreload_inotify.c create mode 100644 suckless/sxiv/autoreload_nop.c create mode 100644 suckless/sxiv/commands.c create mode 100644 suckless/sxiv/commands.lst create mode 100644 suckless/sxiv/config.def.h create mode 100644 suckless/sxiv/config.h create mode 100755 suckless/sxiv/exec/image-info create mode 100755 suckless/sxiv/exec/key-handler create mode 100644 suckless/sxiv/icon/128x128.png create mode 100644 suckless/sxiv/icon/16x16.png create mode 100644 suckless/sxiv/icon/32x32.png create mode 100644 suckless/sxiv/icon/48x48.png create mode 100644 suckless/sxiv/icon/64x64.png create mode 100644 suckless/sxiv/icon/Makefile create mode 100644 suckless/sxiv/icon/dat2h.awk create mode 100644 suckless/sxiv/icon/data.h create mode 100644 suckless/sxiv/image.c create mode 100644 suckless/sxiv/main.c create mode 100644 suckless/sxiv/options.c create mode 100755 suckless/sxiv/sxiv create mode 100644 suckless/sxiv/sxiv.1 create mode 100644 suckless/sxiv/sxiv.desktop create mode 100644 suckless/sxiv/sxiv.h create mode 100644 suckless/sxiv/thumbs.c create mode 100644 suckless/sxiv/utf8.h create mode 100644 suckless/sxiv/util.c create mode 100644 suckless/sxiv/version.h create mode 100644 suckless/sxiv/window.c create mode 100644 suckless/xbanish/.gitignore create mode 100644 suckless/xbanish/LICENSE create mode 100644 suckless/xbanish/Makefile create mode 100755 suckless/xbanish/xbanish create mode 100644 suckless/xbanish/xbanish.1 create mode 100644 suckless/xbanish/xbanish.c diff --git a/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h b/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h index 49283b3f..91490102 100644 --- a/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h +++ b/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h @@ -140,7 +140,7 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | */ static SiteSpecific styles[] = { /* regexp file in $styledir */ - { ".*", "localwiki.css" }, + { "/usr/share/doc/arch-wiki/html/en/.*", "wiki.css" }, }; /* certificates */ @@ -155,22 +155,15 @@ static SiteSpecific certs[] = { #define MODKEY GDK_CONTROL_MASK static char *linkselect_curwin [] = { "/bin/sh", "-c", - "surf_linkselect.sh $0 'Link' | xargs -r xprop -id $0 -f _SURF_GO 8s -set _SURF_GO", + "/home/yigit/.local/bin/surf_linkselect.sh $0 'Link' | xargs -r xprop -id $0 -f _SURF_GO 8s -set _SURF_GO", winid, NULL }; -static char *linkselect_newwin [] = { "/bin/sh", "-c", - "surf_linkselect.sh $0 'Link (new window)' | xargs -r /home/yigit/.scripts/tabbed_surf", +static char *linkyank [] = { "/bin/sh", "-c", + "/home/yigit/.local/bin/surf_linkselect.sh $0 'Link (y)' | xargs xclip -selection clipboard", winid, NULL }; -static char *editscreen[] = { "/bin/sh", "-c", "edit_screen.sh", NULL }; - -#define WATCH {.v = (char *[]){ "/bin/sh", "-c", \ - "/home/yigit/.scripts/watch_mpv.sh $(xprop -id $0 _SURF_URI | cut -d \\\" -f 2)", \ - winid, NULL } } -#define WALLABAG {.v = (char *[]){ "/bin/sh", "-c", \ - "wallabag add $(xprop -id $0 _SURF_URI | cut -d '\"' -f 2) ; echo test > /tmp/aaa", \ - winid, NULL }} +static char *editscreen[] = { "/bin/sh", "-c", "edit_screen.sh", NULL }; /* hotkeys */ /* @@ -181,8 +174,9 @@ static Key keys[] = { /* modifier keyval function arg */ { 0, GDK_KEY_i, insert, { .i = 1 } }, { 0, GDK_KEY_Escape, insert, { .i = 0 } }, - { 0, GDK_KEY_o, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_o, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { 0, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, { MODKEY, GDK_KEY_b, spawn, BM_ADD("_SURF_URI") }, { 0, GDK_KEY_Escape, stop, { 0 } }, @@ -228,11 +222,9 @@ static Key keys[] = { { MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, { MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, { MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, - { MODKEY, GDK_KEY_d, externalpipe, { .v = linkselect_curwin } }, - { GDK_SHIFT_MASK|MODKEY, GDK_KEY_d, externalpipe, { .v = linkselect_newwin } }, - { MODKEY, GDK_KEY_o, externalpipe, { .v = editscreen } }, - { MODKEY|GDK_SHIFT_MASK, GDK_KEY_w, spawn, WATCH }, - { MODKEY, GDK_KEY_m, spawn, WALLABAG }, + { MODKEY, GDK_KEY_u, externalpipe, { .v = linkselect_curwin } }, + { MODKEY, GDK_KEY_y, externalpipe, { .v = linkyank } }, + { MODKEY, GDK_KEY_v, externalpipe, { .v = editscreen } }, { MODKEY , GDK_KEY_Return, spawn, SETURI("_SURF_GO") }, }; diff --git a/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h.blob b/.ccls-cache/@home@yigit@.dotfiles/suckless@surf@config.h.blob index 4b7bf11b743fb34106cc3dd99b8d9b976259391b..9258b8df1b0c45612009fa4f089372948ea7320a 100644 GIT binary patch delta 389 zcmXBQKS%;$7zXg~yQ>$e&ZH#hN;{mw!KAt2h9HN6rj{rY8xqCBT$+mZAo=#V1|j31 zJ5Nvuf(B0|2ow?j6SznUg6-KGF2C)4c;4q-dU1f8{Q53xaj4TZc0PY=>#g?O9PN>w zn+TL6B>p8^N4Al~QFNb-DKS2&mp2O28{D-+18qU3I_RhY2AY8knuRQygB)stiRK}X z7NCF@p@^2CgqER!az*C66y-{#Y;|1R n>g1?OROkN@s|(+}5jL1ty9w z|De<5a`rgS!&%)4e_&XM1nABly(j6_ru!;3#fL)tO@4&(em^yxx*dy0W7I0Z+CjZn z-2;7I_rL`@N`1FPYV}jIhc#2{FzxD~-8A3}MyVJ`i1aMAi{Y4^=Xti1deo|v5E1;H z^pe}b9F~9VaxgAqp5za3xIhYEkrcraDS>5D24~0& zSRoZ~mdt{4WDcAs^WXwm0IQ@5E|Nv*sggZMzQe;3Spt{IGPpukz?!M%;P5J0g>{|O z!3Jr7&D~8shu7FO8;94~b#Q~-kju)4*qqDe-f?(qRQuZhrn`7|(0Q%5A9AYy=7f!% zmMYdBjD`dDJHm({+HDD6T!@JYdx{r*L2o#gv?YT7QP|`0ufG*+cBqfQw9c{ zX4^6A&B()vtj3HxVa0X?% zq)(a`Qt#fHq7W)bR!w%|+k?ZcBP|PqTc~B({-qFnLcd>i<@3jV(xm3lu}B~VmwSB( U84N6pk~XBzFgT6UJ?+E40hg@dB>(^b diff --git a/arch-setup/packages.rice b/arch-setup/packages.rice index 6059ebb7..38682235 100644 --- a/arch-setup/packages.rice +++ b/arch-setup/packages.rice @@ -24,7 +24,9 @@ nvim mpd mpc mpc-mpris -xbanish-timeout +libxfixes +libxi +libxt htop dunst mpv-mpris @@ -37,5 +39,10 @@ clipmenu clipnotify polkit-dumb-agent feh -sxiv +imlib2 +desktop-file-utils +xdg-utils +hicolor-icon-theme +libexif +libxft udevil diff --git a/bash_logout b/bash_logout index a256973c..9bf6870f 100755 --- a/bash_logout +++ b/bash_logout @@ -1,3 +1,4 @@ #!/bin/bash +date > $HOME/last ~/.local/bin/firefox-sync diff --git a/config/X11/xinitrc b/config/X11/xinitrc index 2d1da209..e9ab5c6b 100755 --- a/config/X11/xinitrc +++ b/config/X11/xinitrc @@ -50,7 +50,7 @@ curl 'http://yeetclock/setcolor?R=136&G=192&B=208' & dunst & -xbanish -t 2000 -s & +xbanish -s & pactl upload-sample /usr/share/sounds/freedesktop/stereo/bell.oga x11-bell pactl load-module module-x11-bell sample=x11-bell display=$DISPLAY @@ -64,7 +64,7 @@ xset dpms 600 600 600 $BROWSER & -bitwarden-desktop & +#bitwarden-desktop & touch ~/.cache/dwm-restart while [ -f /home/yigit/.cache/dwm-restart ]; diff --git a/config/surf/styles/default.css b/config/surf/styles/wiki.css similarity index 100% rename from config/surf/styles/default.css rename to config/surf/styles/wiki.css diff --git a/config/zsh/profile b/config/zsh/profile deleted file mode 100755 index 62119fc7..00000000 --- a/config/zsh/profile +++ /dev/null @@ -1,18 +0,0 @@ -# vim:ft=bash -# Environment variables -export GOPATH=$HOME/go -export _JAVA_AWT_WM_NONREPARENTING=1 -export AWT_TOOLKIT=MToolkit -export ANDROID_HOME=~/Android/Sdk -export FLUTTER_HOME=~/flutter -export TMUX_PLUGIN_MANAGER_PATH=~/.tmux/plugins -export BORG_KEYS_DIR=~/.keys/borg -export BROWSER=brave -export DEFAULT_RECIPIENT="yigitcolakoglu@hotmail.com" -export EDITOR=vim - -# Setup PATH -export PATH=$PATH:$ANDROID_HOME/tools -export PATH=$PATH:$ANDROID_HOME/platform-tools -export PATH=$PATH:$FLUTTER_HOME/bin -export PATH="$PATH:/home/yigit/.local/bin:/home/yigit/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport:/home/yigit/.local/bin" diff --git a/local/bin/surf_linkselect.sh b/local/bin/surf_linkselect.sh index 2fe11384..050aabc8 100755 --- a/local/bin/surf_linkselect.sh +++ b/local/bin/surf_linkselect.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/bin/bash # surf_linkselect.sh: # Usage: curl somesite.com | surf_linkselect [SURFWINDOWID] [PROMPT] # Deps: xmllint, dmenu @@ -73,4 +73,4 @@ function link_select() { link_normalize $(xprop -id $SURF_WINDOW _SURF_URI | cut -d '"' -f 2) } -link_select \ No newline at end of file +link_select diff --git a/notes/firefox.md b/notes/firefox.md index f9b6de24..b318cfb6 100644 --- a/notes/firefox.md +++ b/notes/firefox.md @@ -7,3 +7,7 @@ Here are some `about:config` variables that you might want to set: | services.sync.prefs.sync.browser.uiCustomization.state | true | | extensions.pocket.enabled | false | | browser.cache.disk.parent_directory | /run/user/*UID*/firefox | +| browser.sessionstore.resume_from_crash | false | +| browser.cache.disk.enable | false | +| browser.cache.memory.enable | true | +| browser.cache.memory.capacity | 512 | diff --git a/profile b/profile index 175c4ce4..47610d58 100755 --- a/profile +++ b/profile @@ -1,26 +1,18 @@ #!/bin/bash -# Environment variables +# Fixes for some bugs export QT_QPA_PLATFORMTHEME="qt5ct" export _JAVA_AWT_WM_NONREPARENTING=1 export AWT_TOOLKIT=MToolkit + +# Environment variables export SHELL=/bin/zsh export TERMINAL=/usr/local/bin/st -export TMUX_PLUGIN_MANAGER_PATH=~/.tmux/plugins -export BORG_KEYS_DIR=~/.keys/borg export BROWSER=firefox export EDITOR=nvim export OPENER=xdg-open -export ANDROID_HOME=/home/yigit/Android export DEFAULT_RECIPIENT="yigitcolakoglu@hotmail.com" -# Setup PATH -export PATH=$ANDROID_HOME/tools:$PATH -export PATH=$ANDROID_HOME/platform-tools:$PATH -export PATH=$FLUTTER_HOME/bin:$PATH -export PATH="$PATH:$HOME/.local/bin:$HOME/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport" -export CPATH=/usr/include/opencv4 - # Set XDG Directories export XDG_DATA_HOME="$HOME"/.local/share export XDG_CONFIG_HOME="$HOME"/.config @@ -29,17 +21,9 @@ export XDG_DATA_DIRS="/usr/local/share:/usr/share" export XDG_CONFIG_DIRS="/etc/xdg" export XDG_RUNTIME_DIR="/run/user/1000" -# LF Icons -LF_ICONS=$(sed ~/.config/lf/diricons \ - -e '/^[ \t]*#/d' \ - -e '/^[ \t]*$/d' \ - -e 's/[ \t]\+/=/g' \ - -e 's/$/ /') -LF_ICONS=${LF_ICONS//$'\n'/:} - -export LF_ICONS - # Cleanup Home Directory +export TMUX_PLUGIN_MANAGER_PATH="$XDG_DATA_HOME"/tmux/plugins +export BORG_KEYS_DIR="$XDG_DATA_HOME"/keys/borg export CARGO_HOME="$XDG_DATA_HOME"/cargo export GOPATH="$XDG_DATA_HOME"/go export ANDROID_HOME="$XDG_DATA_HOME"/Sdk @@ -72,20 +56,43 @@ export XSERVERRC="$XDG_CONFIG_HOME"/X11/xserverrc export XINITRC="$XDG_CONFIG_HOME"/X11/xinitrc export XAUTHORITY="$XDG_RUNTIME_DIR"/Xauthority export INPUTRC="$XDG_CONFIG_HOME"/readline/inputrc +export PASSWORD_STORE_DIR="$XDG_DATA_HOME"/pass +export TMUX_TMPDIR="$XDG_RUNTIME_DIR" + + +# Setup PATH +export PATH=$ANDROID_HOME/tools:$PATH +export PATH=$ANDROID_HOME/platform-tools:$PATH +export PATH=$FLUTTER_HOME/bin:$PATH +export PATH="$PATH:$HOME/.local/bin:$HOME/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport" +export CPATH=/usr/include/opencv4 + +# Setup LF Icons (Doing this everytime lf start might cause some overhead) +LF_ICONS=$(sed ~/.config/lf/diricons \ + -e '/^[ \t]*#/d' \ + -e '/^[ \t]*$/d' \ + -e 's/[ \t]\+/=/g' \ + -e 's/$/ /') +LF_ICONS=${LF_ICONS//$'\n'/:} + +export LF_ICONS +# Setup dbus case "$(readlink -f /sbin/init)" in *systemd*) export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus ;; + *) eval "$(dbus-launch --exit-with-session --sh-syntax)" esac +# Setup SSH if [ ! "$SSH_AUTH_SOCK" ]; then eval "$(ssh-agent | head -n 2)" grep -slR "PRIVATE" ~/.ssh/ | xargs ssh-add > /dev/null 2> /dev/null & fi +# Start xinit if logged in from tty1 if [ "$DISPLAY" = "" ] && [ "$(tty)" = /dev/tty1 ]; then if [ "$DBUS_SESSION_BUS_ADDRESS" = "" ] && [ ! $(command -v dbus-launch) = "" ]; then sleep 2 - eval "$(dbus-launch --exit-with-session --sh-syntax)" exec xinit 2> $XDG_RUNTIME_DIR/xinit.err > $XDG_RUNTIME_DIR/xinit || exit else sleep 2 diff --git a/suckless/surf/config.h b/suckless/surf/config.h index 706f7bf9..348cf612 100644 --- a/suckless/surf/config.h +++ b/suckless/surf/config.h @@ -140,7 +140,7 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | */ static SiteSpecific styles[] = { /* regexp file in $styledir */ - { ".*", "default.css" }, + { "/usr/share/doc/arch-wiki/html/en/.*", "wiki.css" }, }; /* certificates */ @@ -155,22 +155,15 @@ static SiteSpecific certs[] = { #define MODKEY GDK_CONTROL_MASK static char *linkselect_curwin [] = { "/bin/sh", "-c", - "surf_linkselect.sh $0 'Link' | xargs -r xprop -id $0 -f _SURF_GO 8s -set _SURF_GO", + "/home/yigit/.local/bin/surf_linkselect.sh $0 'Link' | xargs -r xprop -id $0 -f _SURF_GO 8s -set _SURF_GO", winid, NULL }; -static char *linkselect_newwin [] = { "/bin/sh", "-c", - "surf_linkselect.sh $0 'Link (new window)' | xargs -r /home/yigit/.scripts/tabbed_surf", +static char *linkyank [] = { "/bin/sh", "-c", + "/home/yigit/.local/bin/surf_linkselect.sh $0 'Link (y)' | xclip -selection clipboard", winid, NULL }; -static char *editscreen[] = { "/bin/sh", "-c", "edit_screen.sh", NULL }; - -#define WATCH {.v = (char *[]){ "/bin/sh", "-c", \ - "/home/yigit/.scripts/watch_mpv.sh $(xprop -id $0 _SURF_URI | cut -d \\\" -f 2)", \ - winid, NULL } } -#define WALLABAG {.v = (char *[]){ "/bin/sh", "-c", \ - "wallabag add $(xprop -id $0 _SURF_URI | cut -d '\"' -f 2) ; echo test > /tmp/aaa", \ - winid, NULL }} +static char *editscreen[] = { "/bin/sh", "-c", "edit_screen.sh", NULL }; /* hotkeys */ /* @@ -181,8 +174,9 @@ static Key keys[] = { /* modifier keyval function arg */ { 0, GDK_KEY_i, insert, { .i = 1 } }, { 0, GDK_KEY_Escape, insert, { .i = 0 } }, - { 0, GDK_KEY_o, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, + { MODKEY, GDK_KEY_o, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, + { 0, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, { MODKEY, GDK_KEY_b, spawn, BM_ADD("_SURF_URI") }, { 0, GDK_KEY_Escape, stop, { 0 } }, @@ -228,11 +222,9 @@ static Key keys[] = { { MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, { MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, { MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, - { MODKEY, GDK_KEY_d, externalpipe, { .v = linkselect_curwin } }, - { GDK_SHIFT_MASK|MODKEY, GDK_KEY_d, externalpipe, { .v = linkselect_newwin } }, - { MODKEY, GDK_KEY_o, externalpipe, { .v = editscreen } }, - { MODKEY|GDK_SHIFT_MASK, GDK_KEY_w, spawn, WATCH }, - { MODKEY, GDK_KEY_m, spawn, WALLABAG }, + { MODKEY, GDK_KEY_u, externalpipe, { .v = linkselect_curwin } }, + { MODKEY, GDK_KEY_y, externalpipe, { .v = linkyank } }, + { MODKEY, GDK_KEY_v, externalpipe, { .v = editscreen } }, { MODKEY , GDK_KEY_Return, spawn, SETURI("_SURF_GO") }, }; diff --git a/suckless/sxiv/.gitignore b/suckless/sxiv/.gitignore new file mode 100644 index 00000000..e3c353ed --- /dev/null +++ b/suckless/sxiv/.gitignore @@ -0,0 +1,57 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +slock + +*.orig +*.rej diff --git a/suckless/sxiv/LICENSE b/suckless/sxiv/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/suckless/sxiv/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/suckless/sxiv/Makefile b/suckless/sxiv/Makefile new file mode 100644 index 00000000..da6c209f --- /dev/null +++ b/suckless/sxiv/Makefile @@ -0,0 +1,88 @@ +version = 26 + +srcdir = . +VPATH = $(srcdir) + +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +# autoreload backend: inotify/nop +AUTORELOAD = inotify + +# enable features requiring giflib (-lgif) +HAVE_GIFLIB = 1 + +# enable features requiring libexif (-lexif) +HAVE_LIBEXIF = 1 + +cflags = -std=c99 -Wall -pedantic $(CFLAGS) +cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \ + -DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ + -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 + +lib_exif_0 = +lib_exif_1 = -lexif +lib_gif_0 = +lib_gif_1 = -lgif +ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ + $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) + +objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \ + thumbs.o util.o window.o + +all: sxiv + +.PHONY: all clean install uninstall +.SUFFIXES: +.SUFFIXES: .c .o +$(V).SILENT: + +sxiv: $(objs) + @echo "LINK $@" + $(CC) $(LDFLAGS) -o $@ $(objs) $(ldlibs) + +$(objs): Makefile sxiv.h commands.lst config.h +options.o: version.h +window.o: icon/data.h + +.c.o: + @echo "CC $@" + $(CC) $(cflags) $(cppflags) -c -o $@ $< + +config.h: + @echo "GEN $@" + cp $(srcdir)/config.def.h $@ + +version.h: Makefile .git/index + @echo "GEN $@" + v="$$(cd $(srcdir); git describe 2>/dev/null)"; \ + echo "#define VERSION \"$${v:-$(version)}\"" >$@ + +.git/index: + +clean: + rm -f *.o sxiv + +install: all + @echo "INSTALL bin/sxiv" + mkdir -p $(DESTDIR)$(PREFIX)/bin + cp sxiv $(DESTDIR)$(PREFIX)/bin/ + chmod 755 $(DESTDIR)$(PREFIX)/bin/sxiv + @echo "INSTALL sxiv.1" + mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + sed "s!PREFIX!$(PREFIX)!g; s!VERSION!$(version)!g" sxiv.1 \ + >$(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + chmod 644 $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + @echo "INSTALL share/sxiv/" + mkdir -p $(DESTDIR)$(PREFIX)/share/sxiv/exec + cp exec/* $(DESTDIR)$(PREFIX)/share/sxiv/exec/ + chmod 755 $(DESTDIR)$(PREFIX)/share/sxiv/exec/* + +uninstall: + @echo "REMOVE bin/sxiv" + rm -f $(DESTDIR)$(PREFIX)/bin/sxiv + @echo "REMOVE sxiv.1" + rm -f $(DESTDIR)$(MANPREFIX)/man1/sxiv.1 + @echo "REMOVE share/sxiv/" + rm -rf $(DESTDIR)$(PREFIX)/share/sxiv + diff --git a/suckless/sxiv/TODO b/suckless/sxiv/TODO new file mode 100644 index 00000000..36a038c6 --- /dev/null +++ b/suckless/sxiv/TODO @@ -0,0 +1,5 @@ +- Load all frames from TIFF files. We have to write our own loader for this to + happen--just like we did for GIF images--because Imlib2 does not support + multiple frames. Issue #241. +- Add support for more embedded thumbnail formats. Right now, sxiv seems to use + the smallest one. Issue #238. diff --git a/suckless/sxiv/autoreload_inotify.c b/suckless/sxiv/autoreload_inotify.c new file mode 100644 index 00000000..7c5b09ab --- /dev/null +++ b/suckless/sxiv/autoreload_inotify.c @@ -0,0 +1,112 @@ +/* Copyright 2017 Max Voit, Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" + +#include +#include +#include +#include +#include + +void arl_init(arl_t *arl) +{ + arl->fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); + arl->wd_dir = arl->wd_file = -1; + if (arl->fd == -1) + error(0, 0, "Could not initialize inotify, no automatic image reloading"); +} + +CLEANUP void arl_cleanup(arl_t *arl) +{ + if (arl->fd != -1) + close(arl->fd); + free(arl->filename); +} + +static void rm_watch(int fd, int *wd) +{ + if (*wd != -1) { + inotify_rm_watch(fd, *wd); + *wd = -1; + } +} + +static void add_watch(int fd, int *wd, const char *path, uint32_t mask) +{ + *wd = inotify_add_watch(fd, path, mask); + if (*wd == -1) + error(0, errno, "inotify: %s", path); +} + +void arl_setup(arl_t *arl, const char *filepath) +{ + char *base = strrchr(filepath, '/'); + + if (arl->fd == -1) + return; + + rm_watch(arl->fd, &arl->wd_dir); + rm_watch(arl->fd, &arl->wd_file); + + add_watch(arl->fd, &arl->wd_file, filepath, IN_CLOSE_WRITE | IN_DELETE_SELF); + + free(arl->filename); + arl->filename = estrdup(filepath); + + if (base != NULL) { + arl->filename[++base - filepath] = '\0'; + add_watch(arl->fd, &arl->wd_dir, arl->filename, IN_CREATE | IN_MOVED_TO); + strcpy(arl->filename, base); + } +} + +union { + char d[4096]; /* aligned buffer */ + struct inotify_event e; +} buf; + +bool arl_handle(arl_t *arl) +{ + bool reload = false; + char *ptr; + const struct inotify_event *e; + + for (;;) { + ssize_t len = read(arl->fd, buf.d, sizeof(buf.d)); + + if (len == -1) { + if (errno == EINTR) + continue; + break; + } + for (ptr = buf.d; ptr < buf.d + len; ptr += sizeof(*e) + e->len) { + e = (const struct inotify_event*) ptr; + if (e->wd == arl->wd_file && (e->mask & IN_CLOSE_WRITE)) { + reload = true; + } else if (e->wd == arl->wd_file && (e->mask & IN_DELETE_SELF)) { + rm_watch(arl->fd, &arl->wd_file); + } else if (e->wd == arl->wd_dir && (e->mask & (IN_CREATE | IN_MOVED_TO))) { + if (STREQ(e->name, arl->filename)) + reload = true; + } + } + } + return reload; +} + diff --git a/suckless/sxiv/autoreload_nop.c b/suckless/sxiv/autoreload_nop.c new file mode 100644 index 00000000..a0427af6 --- /dev/null +++ b/suckless/sxiv/autoreload_nop.c @@ -0,0 +1,42 @@ +/* Copyright 2017 Max Voit + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" + +void arl_init(arl_t *arl) +{ + arl->fd = -1; +} + +void arl_cleanup(arl_t *arl) +{ + (void) arl; +} + +void arl_setup(arl_t *arl, const char *filepath) +{ + (void) arl; + (void) filepath; +} + +bool arl_handle(arl_t *arl) +{ + (void) arl; + return false; +} + diff --git a/suckless/sxiv/commands.c b/suckless/sxiv/commands.c new file mode 100644 index 00000000..f685bc03 --- /dev/null +++ b/suckless/sxiv/commands.c @@ -0,0 +1,455 @@ +/* Copyright 2011, 2012, 2014 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" + +#include +#include +#include +#include + +void remove_file(int, bool); +void load_image(int); +bool mark_image(int, bool); +void close_info(void); +void open_info(void); +int ptr_third_x(void); +void redraw(void); +void reset_cursor(void); +void animate(void); +void slideshow(void); +void set_timeout(timeout_f, int, bool); +void reset_timeout(timeout_f); + +extern appmode_t mode; +extern img_t img; +extern tns_t tns; +extern win_t win; + +extern fileinfo_t *files; +extern int filecnt, fileidx; +extern int alternate; +extern int markcnt; +extern int markidx; + +extern int prefix; +extern bool extprefix; + +bool cg_quit(arg_t _) +{ + unsigned int i; + + if (options->to_stdout && markcnt > 0) { + for (i = 0; i < filecnt; i++) { + if (files[i].flags & FF_MARK) + printf("%s\n", files[i].name); + } + } + exit(EXIT_SUCCESS); +} + +bool cg_switch_mode(arg_t _) +{ + if (mode == MODE_IMAGE) { + if (tns.thumbs == NULL) + tns_init(&tns, files, &filecnt, &fileidx, &win); + img_close(&img, false); + reset_timeout(reset_cursor); + if (img.ss.on) { + img.ss.on = false; + reset_timeout(slideshow); + } + tns.dirty = true; + mode = MODE_THUMB; + } else { + load_image(fileidx); + mode = MODE_IMAGE; + } + return true; +} + +bool cg_toggle_fullscreen(arg_t _) +{ + win_toggle_fullscreen(&win); + /* redraw after next ConfigureNotify event */ + set_timeout(redraw, TO_REDRAW_RESIZE, false); + if (mode == MODE_IMAGE) + img.checkpan = img.dirty = true; + else + tns.dirty = true; + return false; +} + +bool cg_toggle_bar(arg_t _) +{ + win_toggle_bar(&win); + if (mode == MODE_IMAGE) { + if (win.bar.h > 0) + open_info(); + else + close_info(); + img.checkpan = img.dirty = true; + } else { + tns.dirty = true; + } + return true; +} + +bool cg_prefix_external(arg_t _) +{ + extprefix = true; + return false; +} + +bool cg_reload_image(arg_t _) +{ + if (mode == MODE_IMAGE) { + load_image(fileidx); + } else { + win_set_cursor(&win, CURSOR_WATCH); + if (!tns_load(&tns, fileidx, true, false)) { + remove_file(fileidx, false); + tns.dirty = true; + } + } + return true; +} + +bool cg_remove_image(arg_t _) +{ + remove_file(fileidx, true); + if (mode == MODE_IMAGE) + load_image(fileidx); + else + tns.dirty = true; + return true; +} + +bool cg_first(arg_t _) +{ + if (mode == MODE_IMAGE && fileidx != 0) { + load_image(0); + return true; + } else if (mode == MODE_THUMB && fileidx != 0) { + fileidx = 0; + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool cg_n_or_last(arg_t _) +{ + int n = prefix != 0 && prefix - 1 < filecnt ? prefix - 1 : filecnt - 1; + + if (mode == MODE_IMAGE && fileidx != n) { + load_image(n); + return true; + } else if (mode == MODE_THUMB && fileidx != n) { + fileidx = n; + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool cg_scroll_screen(arg_t dir) +{ + if (mode == MODE_IMAGE) + return img_pan(&img, dir, -1); + else + return tns_scroll(&tns, dir, true); +} + +bool cg_zoom(arg_t d) +{ + if (mode == MODE_THUMB) + return tns_zoom(&tns, d); + else if (d > 0) + return img_zoom_in(&img); + else if (d < 0) + return img_zoom_out(&img); + else + return false; +} + +bool cg_toggle_image_mark(arg_t _) +{ + return mark_image(fileidx, !(files[fileidx].flags & FF_MARK)); +} + +bool cg_reverse_marks(arg_t _) +{ + int i; + + for (i = 0; i < filecnt; i++) { + files[i].flags ^= FF_MARK; + markcnt += files[i].flags & FF_MARK ? 1 : -1; + } + if (mode == MODE_THUMB) + tns.dirty = true; + return true; +} + +bool cg_mark_range(arg_t _) +{ + int d = markidx < fileidx ? 1 : -1, end, i; + bool dirty = false, on = !!(files[markidx].flags & FF_MARK); + + for (i = markidx + d, end = fileidx + d; i != end; i += d) + dirty |= mark_image(i, on); + return dirty; +} + +bool cg_unmark_all(arg_t _) +{ + int i; + + for (i = 0; i < filecnt; i++) + files[i].flags &= ~FF_MARK; + markcnt = 0; + if (mode == MODE_THUMB) + tns.dirty = true; + return true; +} + +bool cg_navigate_marked(arg_t n) +{ + int d, i; + int new = fileidx; + + if (prefix > 0) + n *= prefix; + d = n > 0 ? 1 : -1; + for (i = fileidx + d; n != 0 && i >= 0 && i < filecnt; i += d) { + if (files[i].flags & FF_MARK) { + n -= d; + new = i; + } + } + if (new != fileidx) { + if (mode == MODE_IMAGE) { + load_image(new); + } else { + fileidx = new; + tns.dirty = true; + } + return true; + } else { + return false; + } +} + +bool cg_change_gamma(arg_t d) +{ + if (img_change_gamma(&img, d * (prefix > 0 ? prefix : 1))) { + if (mode == MODE_THUMB) + tns.dirty = true; + return true; + } else { + return false; + } +} + +bool ci_navigate(arg_t n) +{ + if (prefix > 0) + n *= prefix; + n += fileidx; + if (n < 0) + n = 0; + if (n >= filecnt) + n = filecnt - 1; + + if (n != fileidx) { + load_image(n); + return true; + } else { + return false; + } +} + +bool ci_cursor_navigate(arg_t _) +{ + return ci_navigate(ptr_third_x() - 1); +} + +bool ci_alternate(arg_t _) +{ + load_image(alternate); + return true; +} + +bool ci_navigate_frame(arg_t d) +{ + if (prefix > 0) + d *= prefix; + return !img.multi.animate && img_frame_navigate(&img, d); +} + +bool ci_toggle_animation(arg_t _) +{ + bool dirty = false; + + if (img.multi.cnt > 0) { + img.multi.animate = !img.multi.animate; + if (img.multi.animate) { + dirty = img_frame_animate(&img); + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + } else { + reset_timeout(animate); + } + } + return dirty; +} + +bool ci_scroll(arg_t dir) +{ + return img_pan(&img, dir, prefix); +} + +bool ci_scroll_to_edge(arg_t dir) +{ + return img_pan_edge(&img, dir); +} + +bool ci_drag(arg_t mode) +{ + int x, y, ox, oy; + float px, py; + XEvent e; + + if ((int)(img.w * img.zoom) <= win.w && (int)(img.h * img.zoom) <= win.h) + return false; + + win_set_cursor(&win, CURSOR_DRAG); + + win_cursor_pos(&win, &x, &y); + ox = x; + oy = y; + + for (;;) { + if (mode == DRAG_ABSOLUTE) { + px = MIN(MAX(0.0, x - win.w*0.1), win.w*0.8) / (win.w*0.8) + * (win.w - img.w * img.zoom); + py = MIN(MAX(0.0, y - win.h*0.1), win.h*0.8) / (win.h*0.8) + * (win.h - img.h * img.zoom); + } else { + px = img.x + x - ox; + py = img.y + y - oy; + } + + if (img_pos(&img, px, py)) { + img_render(&img); + win_draw(&win); + } + XMaskEvent(win.env.dpy, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); + if (e.type == ButtonPress || e.type == ButtonRelease) + break; + while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); + ox = x; + oy = y; + x = e.xmotion.x; + y = e.xmotion.y; + } + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + + return true; +} + +bool ci_set_zoom(arg_t zl) +{ + return img_zoom(&img, (prefix ? prefix : zl) / 100.0); +} + +bool ci_fit_to_win(arg_t sm) +{ + return img_fit_win(&img, sm); +} + +bool ci_rotate(arg_t degree) +{ + img_rotate(&img, degree); + return true; +} + +bool ci_flip(arg_t dir) +{ + img_flip(&img, dir); + return true; +} + +bool ci_toggle_antialias(arg_t _) +{ + img_toggle_antialias(&img); + return true; +} + +bool ci_toggle_alpha(arg_t _) +{ + img.alpha = !img.alpha; + img.dirty = true; + return true; +} + +bool ci_slideshow(arg_t _) +{ + if (prefix > 0) { + img.ss.on = true; + img.ss.delay = prefix * 10; + set_timeout(slideshow, img.ss.delay * 100, true); + } else if (img.ss.on) { + img.ss.on = false; + reset_timeout(slideshow); + } else { + img.ss.on = true; + } + return true; +} + +bool ct_move_sel(arg_t dir) +{ + return tns_move_selection(&tns, dir, prefix); +} + +bool ct_reload_all(arg_t _) +{ + tns_free(&tns); + tns_init(&tns, files, &filecnt, &fileidx, &win); + tns.dirty = true; + return true; +} + + +#undef G_CMD +#define G_CMD(c) { -1, cg_##c }, +#undef I_CMD +#define I_CMD(c) { MODE_IMAGE, ci_##c }, +#undef T_CMD +#define T_CMD(c) { MODE_THUMB, ct_##c }, + +const cmd_t cmds[CMD_COUNT] = { +#include "commands.lst" +}; + diff --git a/suckless/sxiv/commands.lst b/suckless/sxiv/commands.lst new file mode 100644 index 00000000..06e7d78d --- /dev/null +++ b/suckless/sxiv/commands.lst @@ -0,0 +1,37 @@ +G_CMD(quit) +G_CMD(switch_mode) +G_CMD(toggle_fullscreen) +G_CMD(toggle_bar) +G_CMD(prefix_external) +G_CMD(reload_image) +G_CMD(remove_image) +G_CMD(first) +G_CMD(n_or_last) +G_CMD(scroll_screen) +G_CMD(zoom) +G_CMD(toggle_image_mark) +G_CMD(reverse_marks) +G_CMD(mark_range) +G_CMD(unmark_all) +G_CMD(navigate_marked) +G_CMD(change_gamma) + +I_CMD(navigate) +I_CMD(cursor_navigate) +I_CMD(alternate) +I_CMD(navigate_frame) +I_CMD(toggle_animation) +I_CMD(scroll) +I_CMD(scroll_to_edge) +I_CMD(drag) +I_CMD(set_zoom) +I_CMD(fit_to_win) +I_CMD(rotate) +I_CMD(flip) +I_CMD(toggle_antialias) +I_CMD(toggle_alpha) +I_CMD(slideshow) + +T_CMD(move_sel) +T_CMD(reload_all) + diff --git a/suckless/sxiv/config.def.h b/suckless/sxiv/config.def.h new file mode 100644 index 00000000..9981ca3f --- /dev/null +++ b/suckless/sxiv/config.def.h @@ -0,0 +1,152 @@ +#ifdef _WINDOW_CONFIG + +/* default window dimensions (overwritten via -g option): */ +enum { + WIN_WIDTH = 800, + WIN_HEIGHT = 600 +}; + +/* colors and font are configured with 'background', 'foreground' and + * 'font' X resource properties. + * See X(7) section Resources and xrdb(1) for more information. + */ + +#endif +#ifdef _IMAGE_CONFIG + +/* levels (in percent) to use when zooming via '-' and '+': + * (first/last value is used as min/max zoom level) + */ +static const float zoom_levels[] = { + 12.5, 25.0, 50.0, 75.0, + 100.0, 150.0, 200.0, 400.0, 800.0 +}; + +/* default slideshow delay (in sec, overwritten via -S option): */ +enum { SLIDESHOW_DELAY = 5 }; + +/* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and + * (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. + * */ +static const double GAMMA_MAX = 10.0; +static const int GAMMA_RANGE = 32; + +/* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ +static const int PAN_FRACTION = 5; + +/* if false, pixelate images at zoom level != 100%, + * toggled with 'a' key binding + */ +static const bool ANTI_ALIAS = true; + +/* if true, use a checkerboard background for alpha layer, + * toggled with 'A' key binding + */ +static const bool ALPHA_LAYER = false; + +#endif +#ifdef _THUMBS_CONFIG + +/* thumbnail sizes in pixels (width == height): */ +static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; + +/* thumbnail size at startup, index into thumb_sizes[]: */ +static const int THUMB_SIZE = 3; + +#endif +#ifdef _MAPPINGS_CONFIG + +/* keyboard mappings for image and thumbnail mode: */ +static const keymap_t keys[] = { + /* modifiers key function argument */ + { 0, XK_q, g_quit, None }, + { 0, XK_Return, g_switch_mode, None }, + { 0, XK_f, g_toggle_fullscreen, None }, + { 0, XK_b, g_toggle_bar, None }, + { ControlMask, XK_x, g_prefix_external, None }, + { 0, XK_g, g_first, None }, + { 0, XK_G, g_n_or_last, None }, + { 0, XK_r, g_reload_image, None }, + { 0, XK_D, g_remove_image, None }, + { ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_k, g_scroll_screen, DIR_UP }, + { ControlMask, XK_Up, g_scroll_screen, DIR_UP }, + { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, + { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, + { 0, XK_plus, g_zoom, +1 }, + { 0, XK_KP_Add, g_zoom, +1 }, + { 0, XK_minus, g_zoom, -1 }, + { 0, XK_KP_Subtract, g_zoom, -1 }, + { 0, XK_m, g_toggle_image_mark, None }, + { 0, XK_M, g_mark_range, None }, + { ControlMask, XK_m, g_reverse_marks, None }, + { ControlMask, XK_u, g_unmark_all, None }, + { 0, XK_N, g_navigate_marked, +1 }, + { 0, XK_P, g_navigate_marked, -1 }, + { 0, XK_braceleft, g_change_gamma, -1 }, + { 0, XK_braceright, g_change_gamma, +1 }, + { ControlMask, XK_g, g_change_gamma, 0 }, + + { 0, XK_h, t_move_sel, DIR_LEFT }, + { 0, XK_Left, t_move_sel, DIR_LEFT }, + { 0, XK_j, t_move_sel, DIR_DOWN }, + { 0, XK_Down, t_move_sel, DIR_DOWN }, + { 0, XK_k, t_move_sel, DIR_UP }, + { 0, XK_Up, t_move_sel, DIR_UP }, + { 0, XK_l, t_move_sel, DIR_RIGHT }, + { 0, XK_Right, t_move_sel, DIR_RIGHT }, + { 0, XK_R, t_reload_all, None }, + + { 0, XK_n, i_navigate, +1 }, + { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_space, i_navigate, +1 }, + { 0, XK_p, i_navigate, -1 }, + { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_BackSpace, i_navigate, -1 }, + { 0, XK_bracketright, i_navigate, +10 }, + { 0, XK_bracketleft, i_navigate, -10 }, + { ControlMask, XK_6, i_alternate, None }, + { ControlMask, XK_n, i_navigate_frame, +1 }, + { ControlMask, XK_p, i_navigate_frame, -1 }, + { ControlMask, XK_space, i_toggle_animation, None }, + { 0, XK_h, i_scroll, DIR_LEFT }, + { 0, XK_Left, i_scroll, DIR_LEFT }, + { 0, XK_j, i_scroll, DIR_DOWN }, + { 0, XK_Down, i_scroll, DIR_DOWN }, + { 0, XK_k, i_scroll, DIR_UP }, + { 0, XK_Up, i_scroll, DIR_UP }, + { 0, XK_l, i_scroll, DIR_RIGHT }, + { 0, XK_Right, i_scroll, DIR_RIGHT }, + { 0, XK_H, i_scroll_to_edge, DIR_LEFT }, + { 0, XK_J, i_scroll_to_edge, DIR_DOWN }, + { 0, XK_K, i_scroll_to_edge, DIR_UP }, + { 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, + { 0, XK_equal, i_set_zoom, 100 }, + { 0, XK_w, i_fit_to_win, SCALE_DOWN }, + { 0, XK_W, i_fit_to_win, SCALE_FIT }, + { 0, XK_e, i_fit_to_win, SCALE_WIDTH }, + { 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, + { 0, XK_less, i_rotate, DEGREE_270 }, + { 0, XK_greater, i_rotate, DEGREE_90 }, + { 0, XK_question, i_rotate, DEGREE_180 }, + { 0, XK_bar, i_flip, FLIP_HORIZONTAL }, + { 0, XK_underscore, i_flip, FLIP_VERTICAL }, + { 0, XK_a, i_toggle_antialias, None }, + { 0, XK_A, i_toggle_alpha, None }, + { 0, XK_s, i_slideshow, None }, +}; + +/* mouse button mappings for image mode: */ +static const button_t buttons[] = { + /* modifiers button function argument */ + { 0, 1, i_cursor_navigate, None }, + { 0, 2, i_drag, DRAG_ABSOLUTE }, + { 0, 3, g_switch_mode, None }, + { 0, 4, g_zoom, +1 }, + { 0, 5, g_zoom, -1 }, +}; + +#endif diff --git a/suckless/sxiv/config.h b/suckless/sxiv/config.h new file mode 100644 index 00000000..9981ca3f --- /dev/null +++ b/suckless/sxiv/config.h @@ -0,0 +1,152 @@ +#ifdef _WINDOW_CONFIG + +/* default window dimensions (overwritten via -g option): */ +enum { + WIN_WIDTH = 800, + WIN_HEIGHT = 600 +}; + +/* colors and font are configured with 'background', 'foreground' and + * 'font' X resource properties. + * See X(7) section Resources and xrdb(1) for more information. + */ + +#endif +#ifdef _IMAGE_CONFIG + +/* levels (in percent) to use when zooming via '-' and '+': + * (first/last value is used as min/max zoom level) + */ +static const float zoom_levels[] = { + 12.5, 25.0, 50.0, 75.0, + 100.0, 150.0, 200.0, 400.0, 800.0 +}; + +/* default slideshow delay (in sec, overwritten via -S option): */ +enum { SLIDESHOW_DELAY = 5 }; + +/* gamma correction: the user-visible ranges [-GAMMA_RANGE, 0] and + * (0, GAMMA_RANGE] are mapped to the ranges [0, 1], and (1, GAMMA_MAX]. + * */ +static const double GAMMA_MAX = 10.0; +static const int GAMMA_RANGE = 32; + +/* command i_scroll pans image 1/PAN_FRACTION of screen width/height */ +static const int PAN_FRACTION = 5; + +/* if false, pixelate images at zoom level != 100%, + * toggled with 'a' key binding + */ +static const bool ANTI_ALIAS = true; + +/* if true, use a checkerboard background for alpha layer, + * toggled with 'A' key binding + */ +static const bool ALPHA_LAYER = false; + +#endif +#ifdef _THUMBS_CONFIG + +/* thumbnail sizes in pixels (width == height): */ +static const int thumb_sizes[] = { 32, 64, 96, 128, 160 }; + +/* thumbnail size at startup, index into thumb_sizes[]: */ +static const int THUMB_SIZE = 3; + +#endif +#ifdef _MAPPINGS_CONFIG + +/* keyboard mappings for image and thumbnail mode: */ +static const keymap_t keys[] = { + /* modifiers key function argument */ + { 0, XK_q, g_quit, None }, + { 0, XK_Return, g_switch_mode, None }, + { 0, XK_f, g_toggle_fullscreen, None }, + { 0, XK_b, g_toggle_bar, None }, + { ControlMask, XK_x, g_prefix_external, None }, + { 0, XK_g, g_first, None }, + { 0, XK_G, g_n_or_last, None }, + { 0, XK_r, g_reload_image, None }, + { 0, XK_D, g_remove_image, None }, + { ControlMask, XK_h, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_Left, g_scroll_screen, DIR_LEFT }, + { ControlMask, XK_j, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_Down, g_scroll_screen, DIR_DOWN }, + { ControlMask, XK_k, g_scroll_screen, DIR_UP }, + { ControlMask, XK_Up, g_scroll_screen, DIR_UP }, + { ControlMask, XK_l, g_scroll_screen, DIR_RIGHT }, + { ControlMask, XK_Right, g_scroll_screen, DIR_RIGHT }, + { 0, XK_plus, g_zoom, +1 }, + { 0, XK_KP_Add, g_zoom, +1 }, + { 0, XK_minus, g_zoom, -1 }, + { 0, XK_KP_Subtract, g_zoom, -1 }, + { 0, XK_m, g_toggle_image_mark, None }, + { 0, XK_M, g_mark_range, None }, + { ControlMask, XK_m, g_reverse_marks, None }, + { ControlMask, XK_u, g_unmark_all, None }, + { 0, XK_N, g_navigate_marked, +1 }, + { 0, XK_P, g_navigate_marked, -1 }, + { 0, XK_braceleft, g_change_gamma, -1 }, + { 0, XK_braceright, g_change_gamma, +1 }, + { ControlMask, XK_g, g_change_gamma, 0 }, + + { 0, XK_h, t_move_sel, DIR_LEFT }, + { 0, XK_Left, t_move_sel, DIR_LEFT }, + { 0, XK_j, t_move_sel, DIR_DOWN }, + { 0, XK_Down, t_move_sel, DIR_DOWN }, + { 0, XK_k, t_move_sel, DIR_UP }, + { 0, XK_Up, t_move_sel, DIR_UP }, + { 0, XK_l, t_move_sel, DIR_RIGHT }, + { 0, XK_Right, t_move_sel, DIR_RIGHT }, + { 0, XK_R, t_reload_all, None }, + + { 0, XK_n, i_navigate, +1 }, + { 0, XK_n, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_space, i_navigate, +1 }, + { 0, XK_p, i_navigate, -1 }, + { 0, XK_p, i_scroll_to_edge, DIR_LEFT | DIR_UP }, + { 0, XK_BackSpace, i_navigate, -1 }, + { 0, XK_bracketright, i_navigate, +10 }, + { 0, XK_bracketleft, i_navigate, -10 }, + { ControlMask, XK_6, i_alternate, None }, + { ControlMask, XK_n, i_navigate_frame, +1 }, + { ControlMask, XK_p, i_navigate_frame, -1 }, + { ControlMask, XK_space, i_toggle_animation, None }, + { 0, XK_h, i_scroll, DIR_LEFT }, + { 0, XK_Left, i_scroll, DIR_LEFT }, + { 0, XK_j, i_scroll, DIR_DOWN }, + { 0, XK_Down, i_scroll, DIR_DOWN }, + { 0, XK_k, i_scroll, DIR_UP }, + { 0, XK_Up, i_scroll, DIR_UP }, + { 0, XK_l, i_scroll, DIR_RIGHT }, + { 0, XK_Right, i_scroll, DIR_RIGHT }, + { 0, XK_H, i_scroll_to_edge, DIR_LEFT }, + { 0, XK_J, i_scroll_to_edge, DIR_DOWN }, + { 0, XK_K, i_scroll_to_edge, DIR_UP }, + { 0, XK_L, i_scroll_to_edge, DIR_RIGHT }, + { 0, XK_equal, i_set_zoom, 100 }, + { 0, XK_w, i_fit_to_win, SCALE_DOWN }, + { 0, XK_W, i_fit_to_win, SCALE_FIT }, + { 0, XK_e, i_fit_to_win, SCALE_WIDTH }, + { 0, XK_E, i_fit_to_win, SCALE_HEIGHT }, + { 0, XK_less, i_rotate, DEGREE_270 }, + { 0, XK_greater, i_rotate, DEGREE_90 }, + { 0, XK_question, i_rotate, DEGREE_180 }, + { 0, XK_bar, i_flip, FLIP_HORIZONTAL }, + { 0, XK_underscore, i_flip, FLIP_VERTICAL }, + { 0, XK_a, i_toggle_antialias, None }, + { 0, XK_A, i_toggle_alpha, None }, + { 0, XK_s, i_slideshow, None }, +}; + +/* mouse button mappings for image mode: */ +static const button_t buttons[] = { + /* modifiers button function argument */ + { 0, 1, i_cursor_navigate, None }, + { 0, 2, i_drag, DRAG_ABSOLUTE }, + { 0, 3, g_switch_mode, None }, + { 0, 4, g_zoom, +1 }, + { 0, 5, g_zoom, -1 }, +}; + +#endif diff --git a/suckless/sxiv/exec/image-info b/suckless/sxiv/exec/image-info new file mode 100755 index 00000000..da610cfc --- /dev/null +++ b/suckless/sxiv/exec/image-info @@ -0,0 +1,20 @@ +#!/bin/sh + +# Example for $XDG_CONFIG_HOME/sxiv/exec/image-info +# Called by sxiv(1) whenever an image gets loaded. +# The output is displayed in sxiv's status bar. +# Arguments: +# $1: path to image file +# $2: image width +# $3: image height + +s=" " # field separator + +exec 2>/dev/null + +filename=$(basename -- "$1") +filesize=$(du -Hh -- "$1" | cut -f 1) +geometry="${2}x${3}" + +echo "${filesize}${s}${geometry}${s}${filename}" + diff --git a/suckless/sxiv/exec/key-handler b/suckless/sxiv/exec/key-handler new file mode 100755 index 00000000..3b50e4c0 --- /dev/null +++ b/suckless/sxiv/exec/key-handler @@ -0,0 +1,35 @@ +#!/bin/sh + +# Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler +# Called by sxiv(1) after the external prefix key (C-x by default) is pressed. +# The next key combo is passed as its first argument. Passed via stdin are the +# images to act upon, one path per line: all marked images, if in thumbnail +# mode and at least one image has been marked, otherwise the current image. +# sxiv(1) blocks until this script terminates. It then checks which images +# have been modified and reloads them. + +# The key combo argument has the following form: "[C-][M-][S-]KEY", +# where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X +# keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. + +rotate() { + degree="$1" + tr '\n' '\0' | xargs -0 realpath | sort | uniq | while read file; do + case "$(file -b -i "$file")" in + image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; + *) mogrify -rotate "$degree" "$file" ;; + esac + done +} + +case "$1" in +"C-x") xclip -in -filter | tr '\n' ' ' | xclip -in -selection clipboard ;; +"C-c") while read file; do xclip -selection clipboard -target image/png "$file"; done ;; +"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 ;; +"C-g") tr '\n' '\0' | xargs -0 gimp & ;; +"C-r") while read file; do rawtherapee "$file" & done ;; +"C-comma") rotate 270 ;; +"C-period") rotate 90 ;; +"C-slash") rotate 180 ;; +esac + diff --git a/suckless/sxiv/icon/128x128.png b/suckless/sxiv/icon/128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..3076cd73cc5e6ec7dcb139826ac5629d0c3ec5c0 GIT binary patch literal 542 zcmV+(0^$9MP)C0000UP)t-sA|N#X z|Nk&xg8T9|ndMM+LW-<1GB=cNYQ8YpS7XPZvTOhV0lP^=K~#9!?bXq4!!QVi;g)6; z-v5QyPLYmOHgJM1B8~sf@x}SBoZGDKd$)r%paBhNKm+Cjx~}WGZdUg{!u`2(0~*kP z1~lMG0D2b!qVI&%L0YtR`YCr=TaCyM+&*xAWZa@PX(0~S97?6+7G;_Wd zK*azm22e48iUCv)c%TBn!?YS;=g%9a%x4S>_R+Qt0KVQ$o`>L74ESmKJ_K6?*qgf# zz`Fpjw`PCO0U+k=yxm7000Pdg+kNB#AmHpe=4~DT0?w{u-Zlha`hETRS-fr&4*&uA z^R{g`kpX}bk(m!WBLKh(2{0v`auc|-sZ55NE{Ajn<>P+CKPAO_?K;sL}w#Q;E%BuEyJ7sxPxk?2wyxbX!6dSyUf5CgOrpv3^y)dC-&#Q?o5V2Qv2^uGht g0n8LMpaCN28~+5LGexjDkpKVy07*qoM6N<$g1SoBu>b%7 literal 0 HcmV?d00001 diff --git a/suckless/sxiv/icon/16x16.png b/suckless/sxiv/icon/16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..c5cff994990d2b4c2a221846aba72d32fa9bb643 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!T!2rAtCE7r8Y81Jr!Iqprkml( z=2P;r_8H848u0s*+5i9l%QCn?TKzp;978H@)t)>k)}X-Qa?w%X(f@i2rw>mS&CA=- z{QggVvHRYpU%c7pFO;6CnaBQtYwkpq?af@ ZWfKZwTJm{i@>ZbH44$rjF6*2UngC9dLva8A literal 0 HcmV?d00001 diff --git a/suckless/sxiv/icon/32x32.png b/suckless/sxiv/icon/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..01a62f4aaa785e657c48775817dd6616490a5a36 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyasfUeu1X3f|Ns9tNND=~$*jz& zYv$8{HAY6}Q}VL*8Qcs{_Gy&$2db>{ba4!+xOMdGMXm+~p4NwzTC?B(KXh>6vz&=S zPWCU({!9?^_KcA~sIRJaY2V2Mb^Hu_n0T1W@3Z>uj1e5?6i b?B$>5*&xP|!6CI7=miE(S3j3^P6D z2tx1(umwg5djWg`fD#0-2Ve^TN)W&vfI~>-G@u573cw)%sGkO^0&L-1fEzO5C>v3p Ryej|z002ovPDHLkV1hlscg6q! literal 0 HcmV?d00001 diff --git a/suckless/sxiv/icon/Makefile b/suckless/sxiv/icon/Makefile new file mode 100644 index 00000000..bbb47862 --- /dev/null +++ b/suckless/sxiv/icon/Makefile @@ -0,0 +1,12 @@ +PREFIX = /usr/local +ICONS = 16x16.png 32x32.png 48x48.png 64x64.png 128x128.png + +all: + +install: + for f in $(ICONS); do \ + dir="$(DESTDIR)$(PREFIX)/share/icons/hicolor/$${f%.png}/apps"; \ + mkdir -p "$$dir"; \ + cp "$$f" "$$dir/sxiv.png"; \ + chmod 644 "$$dir/sxiv.png"; \ + done diff --git a/suckless/sxiv/icon/dat2h.awk b/suckless/sxiv/icon/dat2h.awk new file mode 100644 index 00000000..cd6f362f --- /dev/null +++ b/suckless/sxiv/icon/dat2h.awk @@ -0,0 +1,35 @@ +#!/usr/bin/awk -f + +function printchars() { + while (n > 0) { + x = n / 16 >= 1 ? 16 : n; + printf("0x%x%x,%s", x - 1, ref[c] - 1, ++i % 12 == 0 ? "\n" : " "); + n -= x; + } +} + +/^$/ { + printchars(); + printf("\n\n"); + c = ""; + i = 0; +} + +/./ { + if (!ref[$0]) { + col[cnt++] = $0; + ref[$0] = cnt; + } + if ($0 != c) { + if (c != "") + printchars(); + c = $0; + n = 0; + } + n++; +} + +END { + for (i = 0; i < cnt; i++) + printf("%s,%s", col[i], ++j % 4 == 0 || i + 1 == cnt ? "\n" : " "); +} diff --git a/suckless/sxiv/icon/data.h b/suckless/sxiv/icon/data.h new file mode 100644 index 00000000..adb44e64 --- /dev/null +++ b/suckless/sxiv/icon/data.h @@ -0,0 +1,247 @@ +#ifndef ICON_DATA_H +#define ICON_DATA_H + +typedef struct { + unsigned int size; + unsigned int cnt; + const unsigned char *data; +} icon_data_t; + +static const unsigned int icon_colors[] = { + 0xff222034, 0xffffffff, 0xff306082, 0xff76428a, + 0xfffbf236, 0xff99e550, 0xffd95763, 0xff37946e, + 0xff6abe30, 0xffac3232 +}; + +static const unsigned char icon_data_16[] = { + 0xf0, 0x80, 0x01, 0xf0, 0x80, 0x21, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x01, 0x40, 0x01, 0x10, 0x01, 0x10, 0x01, 0x00, 0x01, 0x00, 0x01, + 0x30, 0x11, 0x00, 0x01, 0x00, 0x51, 0xf0, 0x22, 0xa0, 0x42, 0x80, 0x62, + 0x03, 0x50, 0x02, 0x34, 0x05, 0x22, 0x13, 0x06, 0x10, 0x37, 0x08, 0x35, + 0x12, 0x13, 0x09, 0x06, 0x22, 0x47, 0x25, 0x02, 0x13, 0x09, 0x06, 0x32, + 0x47, 0x08, 0x05, 0x08, 0x03, 0x19, 0x16, 0x32, 0x47, 0x18, 0x29, 0x16, + 0x42, 0x37, 0x18, 0x19, 0x26, 0x42, 0x47, 0x08 +}; + +static const unsigned char icon_data_32[] = { + 0xf0, 0x10, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xd0, 0x11, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x10, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, + 0x10, 0x11, 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, + 0x90, 0x51, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x10, 0x11, 0x90, 0x11, + 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x00, 0x22, 0x50, 0x11, + 0x30, 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x50, 0x11, 0x30, + 0x11, 0x30, 0x11, 0x10, 0x11, 0x10, 0x11, 0x32, 0x30, 0x31, 0x10, 0x11, + 0x10, 0xb1, 0x52, 0x30, 0x31, 0x10, 0x11, 0x10, 0xb1, 0x52, 0x30, 0x31, + 0x10, 0x11, 0x10, 0xb1, 0x52, 0xf0, 0x10, 0xd2, 0xf0, 0x00, 0xe2, 0xf0, + 0x54, 0x92, 0x13, 0xc0, 0x84, 0x05, 0x62, 0x33, 0x80, 0x74, 0x55, 0x42, + 0x33, 0x09, 0x02, 0x30, 0x77, 0x08, 0x85, 0x32, 0x33, 0x19, 0x12, 0xc7, + 0x08, 0x65, 0x08, 0x12, 0x33, 0x19, 0x06, 0x52, 0x97, 0x08, 0x45, 0x18, + 0x02, 0x33, 0x19, 0x16, 0x62, 0x97, 0x08, 0x35, 0x18, 0x23, 0x29, 0x16, + 0x72, 0x97, 0x18, 0x15, 0x18, 0x23, 0x29, 0x26, 0x72, 0x97, 0x48, 0x13, + 0x39, 0x26, 0x72, 0x97, 0x48, 0x03, 0x39, 0x36, 0x82, 0x97, 0x38, 0x49, + 0x36, 0x82, 0x97, 0x38, 0x39, 0x46, 0x82, 0x97, 0x38, 0x29, 0x56, 0x92, + 0x97, 0x28, 0x29, 0x56, 0x92, 0x97, 0x28 +}; + +static const unsigned char icon_data_48[] = { + 0xf0, 0xa0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xc0, 0x21, 0xf0, + 0xf0, 0xc0, 0x21, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, + 0x20, 0x21, 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, + 0xe0, 0x81, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x81, + 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, + 0x50, 0x21, 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, + 0x20, 0x21, 0x20, 0x21, 0xe0, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, + 0x20, 0x21, 0x10, 0x32, 0x80, 0x21, 0x50, 0x21, 0x50, 0x21, 0x20, 0x21, + 0x20, 0x21, 0x52, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x00, 0x72, + 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, + 0x20, 0xf1, 0x11, 0x82, 0x50, 0x51, 0x20, 0x21, 0x20, 0xf1, 0x11, 0x82, + 0xf0, 0xf0, 0x00, 0xe2, 0xf0, 0xe0, 0xf2, 0x02, 0xf0, 0xc0, 0xf2, 0x22, + 0xf0, 0xb0, 0xf2, 0x32, 0xf0, 0xa0, 0xf2, 0x42, 0xf0, 0x90, 0xf2, 0x52, + 0xf0, 0x80, 0x74, 0x15, 0xc2, 0x03, 0xf0, 0x50, 0xc4, 0x15, 0x92, 0x23, + 0xf0, 0x10, 0xe4, 0x35, 0x72, 0x33, 0x19, 0xc0, 0xc4, 0x85, 0x62, 0x43, + 0x19, 0x12, 0x70, 0x57, 0x18, 0xf5, 0x05, 0x52, 0x53, 0x19, 0x12, 0x40, + 0xb7, 0x18, 0xe5, 0x32, 0x53, 0x29, 0x22, 0xf7, 0x37, 0xb5, 0x08, 0x22, + 0x53, 0x29, 0x06, 0x72, 0xf7, 0xa5, 0x08, 0x12, 0x53, 0x29, 0x16, 0x92, + 0xe7, 0x85, 0x18, 0x02, 0x43, 0x39, 0x26, 0x92, 0xe7, 0x08, 0x65, 0x28, + 0x43, 0x39, 0x26, 0xa2, 0xe7, 0x18, 0x45, 0x28, 0x43, 0x39, 0x36, 0xa2, + 0xe7, 0x28, 0x25, 0x28, 0x33, 0x49, 0x36, 0xb2, 0xe7, 0x78, 0x33, 0x49, + 0x46, 0xb2, 0xe7, 0x68, 0x23, 0x59, 0x46, 0xb2, 0xe7, 0x68, 0x13, 0x59, + 0x56, 0xc2, 0xe7, 0x58, 0x79, 0x56, 0xc2, 0xe7, 0x58, 0x69, 0x66, 0xd2, + 0xd7, 0x58, 0x59, 0x76, 0xd2, 0xd7, 0x58, 0x49, 0x86, 0xe2, 0xe7, 0x38, + 0x39, 0x86, 0xf2, 0xe7, 0x38, 0x29, 0x86, 0xf2, 0x02, 0xe7, 0x38 +}; + +static const unsigned char icon_data_64[] = { + 0xf0, 0xf0, 0x30, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, + 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xb0, 0x31, + 0xf0, 0xf0, 0xf0, 0xb0, 0x31, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, + 0x31, 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, + 0x30, 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x31, 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, + 0x30, 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, + 0x31, 0xf0, 0x30, 0xb1, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, 0x30, 0x31, + 0xf0, 0x30, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x20, + 0x42, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x00, + 0x62, 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, + 0xb0, 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, + 0x31, 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0xb0, 0x31, + 0x70, 0x31, 0x70, 0x31, 0x30, 0x31, 0x30, 0x31, 0x72, 0x70, 0x71, 0x30, + 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, + 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, + 0x31, 0x30, 0xf1, 0x71, 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, + 0xb2, 0x70, 0x71, 0x30, 0x31, 0x30, 0xf1, 0x71, 0xb2, 0xf0, 0xf0, 0x50, + 0xf2, 0x92, 0xf0, 0xf0, 0x30, 0xf2, 0xb2, 0xf0, 0xf0, 0x20, 0xf2, 0xc2, + 0xf0, 0xf0, 0x10, 0xf2, 0xd2, 0xf0, 0xf0, 0x00, 0x94, 0xf2, 0x42, 0xf0, + 0xe0, 0xe4, 0xf2, 0x12, 0x23, 0xf0, 0xa0, 0xf4, 0x14, 0x05, 0xe2, 0x43, + 0xf0, 0x70, 0xf4, 0x14, 0x35, 0xc2, 0x43, 0x19, 0xf0, 0x30, 0xf4, 0x95, + 0xa2, 0x53, 0x29, 0xe0, 0xf4, 0x04, 0xd5, 0x82, 0x63, 0x29, 0x02, 0x90, + 0xc7, 0xf5, 0x65, 0x62, 0x73, 0x29, 0x12, 0x50, 0xf7, 0x27, 0xf5, 0x35, + 0x52, 0x73, 0x29, 0x06, 0x32, 0xf7, 0x87, 0xf5, 0x05, 0x08, 0x42, 0x73, + 0x39, 0x06, 0x32, 0xf7, 0xa7, 0xd5, 0x28, 0x22, 0x73, 0x39, 0x16, 0xa2, + 0xf7, 0x47, 0xb5, 0x38, 0x12, 0x73, 0x39, 0x26, 0xb2, 0xf7, 0x47, 0xa5, + 0x38, 0x02, 0x73, 0x39, 0x26, 0xd2, 0xf7, 0x47, 0x08, 0x75, 0x48, 0x63, + 0x49, 0x36, 0xd2, 0xf7, 0x47, 0x18, 0x65, 0x38, 0x63, 0x49, 0x36, 0xe2, + 0xf7, 0x47, 0x28, 0x45, 0x38, 0x53, 0x59, 0x46, 0xe2, 0xf7, 0x47, 0x38, + 0x15, 0x48, 0x53, 0x59, 0x46, 0xf2, 0xf7, 0x37, 0xa8, 0x43, 0x69, 0x56, + 0xf2, 0xf7, 0x37, 0x98, 0x33, 0x79, 0x56, 0xf2, 0xf7, 0x37, 0x98, 0x23, + 0x79, 0x66, 0xf2, 0x02, 0xf7, 0x37, 0x88, 0x13, 0x89, 0x66, 0xf2, 0x02, + 0xf7, 0x37, 0x88, 0x03, 0x89, 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x99, + 0x76, 0xf2, 0x12, 0xf7, 0x37, 0x78, 0x89, 0x86, 0xf2, 0x12, 0xf7, 0x37, + 0x78, 0x79, 0x96, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, + 0xf7, 0x37, 0x68, 0x69, 0xa6, 0xf2, 0x22, 0xf7, 0x37, 0x68, 0x59, 0xb6, + 0xf2, 0x32, 0xf7, 0x37, 0x58, 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58, + 0x59, 0xb6, 0xf2, 0x32, 0xf7, 0x37, 0x58 +}; + +static const unsigned char icon_data_128[] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0x71, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, + 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, + 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, 0x71, + 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, 0xf1, + 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, 0x70, + 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xf0, 0xf0, + 0x70, 0xf1, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0x70, 0x71, 0xa0, + 0x42, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, 0x71, + 0x60, 0x82, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, 0x70, + 0x71, 0x40, 0xa2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, 0x71, + 0x70, 0x71, 0x20, 0xc2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, 0x70, + 0x71, 0x70, 0x71, 0x00, 0xe2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0x70, 0x71, 0xf0, 0x71, 0xf0, 0x71, + 0x70, 0x71, 0x70, 0x71, 0xf2, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, + 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, + 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, + 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, + 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, + 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, + 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, + 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, + 0xf1, 0x70, 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, + 0x71, 0x70, 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf1, 0x70, 0x71, 0x70, + 0xf1, 0xf1, 0xf1, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, 0xa0, 0xf2, 0xf2, + 0xf2, 0x42, 0xf0, 0xf0, 0xf0, 0xf0, 0x80, 0xf2, 0xf2, 0xf2, 0x62, 0xf0, + 0xf0, 0xf0, 0xf0, 0x70, 0xf2, 0xf2, 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xf0, + 0x60, 0xf2, 0xf2, 0xf2, 0x82, 0xf0, 0xf0, 0xf0, 0xf0, 0x50, 0xf2, 0xf2, + 0xf2, 0x92, 0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0xf2, 0xf2, 0xf2, 0xa2, 0xf0, + 0xf0, 0xf0, 0xf0, 0x30, 0xf2, 0xf2, 0xf2, 0xb2, 0xf0, 0xf0, 0xf0, 0xf0, + 0x30, 0x12, 0xa4, 0xf2, 0xf2, 0xe2, 0xf0, 0xf0, 0xf0, 0xf0, 0x20, 0xf4, + 0x14, 0xf2, 0xf2, 0xa2, 0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0xf4, 0x64, 0xf2, + 0xf2, 0x72, 0xf0, 0xf0, 0xf0, 0xe0, 0xf4, 0xb4, 0xf2, 0xf2, 0x42, 0xf0, + 0xf0, 0xf0, 0xd0, 0xf4, 0xd4, 0x15, 0xf2, 0xf2, 0x12, 0x43, 0xf0, 0xf0, + 0xf0, 0x60, 0xf4, 0xf4, 0x04, 0x35, 0xf2, 0xe2, 0x63, 0xf0, 0xf0, 0xf0, + 0x30, 0xf4, 0xf4, 0x24, 0x45, 0xf2, 0xc2, 0x83, 0xf0, 0xf0, 0xf0, 0x00, + 0xf4, 0xf4, 0x34, 0x65, 0xf2, 0xa2, 0x93, 0xf0, 0xf0, 0xe0, 0xf4, 0xf4, + 0x34, 0x95, 0xf2, 0x82, 0xa3, 0x19, 0xf0, 0xf0, 0x90, 0xf4, 0xf4, 0x44, + 0xc5, 0xf2, 0x62, 0xb3, 0x29, 0xf0, 0xf0, 0x40, 0xf4, 0xf4, 0x64, 0xf5, + 0xf2, 0x42, 0xc3, 0x39, 0xf0, 0xf0, 0xf4, 0xf4, 0x74, 0xf5, 0x35, 0xf2, + 0x22, 0xd3, 0x49, 0xf0, 0xa0, 0xf4, 0xf4, 0x84, 0xf5, 0x75, 0xf2, 0x02, + 0xd3, 0x59, 0x02, 0xf0, 0x50, 0xf7, 0x07, 0xf4, 0x74, 0xf5, 0xb5, 0x08, + 0xe2, 0xe3, 0x59, 0x12, 0xf0, 0x10, 0xf7, 0xd7, 0xf5, 0xf5, 0x95, 0x18, + 0xc2, 0xe3, 0x59, 0x06, 0x22, 0xd0, 0xf7, 0xf7, 0x37, 0xf5, 0xf5, 0x65, + 0x18, 0xb2, 0xf3, 0x59, 0x06, 0x32, 0x80, 0xf7, 0xf7, 0x97, 0xf5, 0xf5, + 0x45, 0x18, 0xa2, 0xf3, 0x59, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x07, 0xf5, + 0xf5, 0x25, 0x28, 0x82, 0xf3, 0x69, 0x16, 0x72, 0xf7, 0xf7, 0xf7, 0x27, + 0xf5, 0xf5, 0x05, 0x38, 0x62, 0xf3, 0x69, 0x26, 0x82, 0xf7, 0xf7, 0xf7, + 0x37, 0xf5, 0xd5, 0x48, 0x52, 0xf3, 0x79, 0x26, 0xc2, 0xf7, 0xf7, 0xf7, + 0x07, 0xf5, 0xb5, 0x58, 0x42, 0xf3, 0x79, 0x36, 0xf2, 0x22, 0xf7, 0xf7, + 0xb7, 0xf5, 0xa5, 0x58, 0x32, 0xf3, 0x79, 0x46, 0xf2, 0x52, 0xf7, 0xf7, + 0x97, 0xf5, 0x85, 0x68, 0x22, 0xf3, 0x89, 0x36, 0xf2, 0x72, 0xf7, 0xf7, + 0x97, 0xf5, 0x65, 0x78, 0x12, 0xf3, 0x89, 0x46, 0xf2, 0x82, 0xf7, 0xf7, + 0x97, 0xf5, 0x55, 0x78, 0x02, 0xf3, 0x89, 0x46, 0xf2, 0xa2, 0xf7, 0xf7, + 0x87, 0x08, 0xf5, 0x35, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xb2, 0xf7, 0xf7, + 0x77, 0x08, 0xf5, 0x25, 0x88, 0xe3, 0x99, 0x56, 0xf2, 0xc2, 0xf7, 0xf7, + 0x77, 0x18, 0xf5, 0x05, 0x88, 0xd3, 0xa9, 0x66, 0xf2, 0xc2, 0xf7, 0xf7, + 0x77, 0x28, 0xf5, 0x78, 0xd3, 0xa9, 0x66, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, + 0x38, 0xd5, 0x78, 0xc3, 0xb9, 0x76, 0xf2, 0xd2, 0xf7, 0xf7, 0x77, 0x48, + 0xa5, 0x88, 0xc3, 0xb9, 0x76, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x58, 0x85, + 0x88, 0xb3, 0xc9, 0x86, 0xf2, 0xe2, 0xf7, 0xf7, 0x77, 0x68, 0x55, 0x98, + 0xb3, 0xc9, 0x86, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0x78, 0x25, 0xa8, 0xa3, + 0xc9, 0xa6, 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x93, 0xd9, 0xa6, + 0xf2, 0xf2, 0xf7, 0xf7, 0x77, 0xf8, 0x48, 0x83, 0xe9, 0xb6, 0xf2, 0xf2, + 0xf7, 0xf7, 0x77, 0xf8, 0x38, 0x73, 0xf9, 0xb6, 0xf2, 0xf2, 0xf7, 0xf7, + 0x77, 0xf8, 0x38, 0x63, 0xf9, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x28, 0x53, 0xf9, 0x09, 0xc6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x28, 0x43, 0xf9, 0x09, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x18, 0x33, 0xf9, 0x19, 0xe6, 0xf2, 0xf2, 0x02, 0xf7, 0xf7, 0x77, + 0xf8, 0x18, 0x23, 0xf9, 0x19, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, + 0xf8, 0x08, 0x13, 0xf9, 0x29, 0xf6, 0xf2, 0xf2, 0x12, 0xf7, 0xf7, 0x77, + 0xf8, 0x08, 0x03, 0xf9, 0x29, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, + 0x77, 0xf8, 0xf9, 0x39, 0xf6, 0x06, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, + 0xf8, 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x22, 0xf7, 0xf7, 0x77, 0xf8, + 0xf9, 0x29, 0xf6, 0x16, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, + 0x19, 0xf6, 0x26, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0x09, + 0xf6, 0x36, 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xe8, 0xf9, 0xf6, 0x56, + 0xf2, 0xf2, 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xe9, 0xf6, 0x66, 0xf2, 0xf2, + 0x32, 0xf7, 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x32, 0xf7, + 0xf7, 0x77, 0xd8, 0xd9, 0xf6, 0x76, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, + 0xc8, 0xc9, 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xc9, + 0xf6, 0x86, 0xf2, 0xf2, 0x42, 0xf7, 0xf7, 0x77, 0xc8, 0xb9, 0xf6, 0x96, + 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, + 0x52, 0xf7, 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, + 0xf7, 0x77, 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, + 0xb8, 0xb9, 0xf6, 0x96, 0xf2, 0xf2, 0x52, 0xf7, 0xf7, 0x77, 0xb8 +}; + +#define ICON_(s) { s, ARRLEN(icon_data_##s), icon_data_##s } + +static const icon_data_t icons[] = { + ICON_(16), + ICON_(32), + ICON_(48), + ICON_(64), + ICON_(128) +}; + +#endif /* ICON_DATA_H */ + diff --git a/suckless/sxiv/image.c b/suckless/sxiv/image.c new file mode 100644 index 00000000..9a9c531b --- /dev/null +++ b/suckless/sxiv/image.c @@ -0,0 +1,797 @@ +/* Copyright 2011, 2012 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#if HAVE_LIBEXIF +#include +#endif + +#if HAVE_GIFLIB +#include +enum { DEF_GIF_DELAY = 75 }; +#endif + +float zoom_min; +float zoom_max; + +static int zoomdiff(img_t *img, float z) +{ + return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom)); +} + +void img_init(img_t *img, win_t *win) +{ + zoom_min = zoom_levels[0] / 100.0; + zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0; + + imlib_context_set_display(win->env.dpy); + imlib_context_set_visual(win->env.vis); + imlib_context_set_colormap(win->env.cmap); + + img->im = NULL; + img->win = win; + img->scalemode = options->scalemode; + img->zoom = options->zoom; + img->zoom = MAX(img->zoom, zoom_min); + img->zoom = MIN(img->zoom, zoom_max); + img->checkpan = false; + img->dirty = false; + img->aa = ANTI_ALIAS; + img->alpha = ALPHA_LAYER; + img->multi.cap = img->multi.cnt = 0; + img->multi.animate = options->animate; + img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0; + img->multi.length = 0; + + img->cmod = imlib_create_color_modifier(); + imlib_context_set_color_modifier(img->cmod); + img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE); + + img->ss.on = options->slideshow > 0; + img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10; +} + +#if HAVE_LIBEXIF +void exif_auto_orientate(const fileinfo_t *file) +{ + ExifData *ed; + ExifEntry *entry; + int byte_order, orientation = 0; + + if ((ed = exif_data_new_from_file(file->path)) == NULL) + return; + byte_order = exif_data_get_byte_order(ed); + entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); + if (entry != NULL) + orientation = exif_get_short(entry->data, byte_order); + exif_data_unref(ed); + + switch (orientation) { + case 5: + imlib_image_orientate(1); + case 2: + imlib_image_flip_vertical(); + break; + case 3: + imlib_image_orientate(2); + break; + case 7: + imlib_image_orientate(1); + case 4: + imlib_image_flip_horizontal(); + break; + case 6: + imlib_image_orientate(1); + break; + case 8: + imlib_image_orientate(3); + break; + } +} +#endif + +#if HAVE_GIFLIB +bool img_load_gif(img_t *img, const fileinfo_t *file) +{ + GifFileType *gif; + GifRowType *rows = NULL; + GifRecordType rec; + ColorMapObject *cmap; + DATA32 bgpixel, *data, *ptr; + DATA32 *prev_frame = NULL; + Imlib_Image im; + int i, j, bg, r, g, b; + int x, y, w, h, sw, sh; + int px, py, pw, ph; + int intoffset[] = { 0, 4, 2, 1 }; + int intjump[] = { 8, 8, 4, 2 }; + int transp = -1; + unsigned int disposal = 0, prev_disposal = 0; + unsigned int delay = 0; + bool err = false; + + if (img->multi.cap == 0) { + img->multi.cap = 8; + img->multi.frames = (img_frame_t*) + emalloc(sizeof(img_frame_t) * img->multi.cap); + } + img->multi.cnt = img->multi.sel = 0; + img->multi.length = 0; + +#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 + gif = DGifOpenFileName(file->path, NULL); +#else + gif = DGifOpenFileName(file->path); +#endif + if (gif == NULL) { + error(0, 0, "%s: Error opening gif image", file->name); + return false; + } + bg = gif->SBackGroundColor; + sw = gif->SWidth; + sh = gif->SHeight; + px = py = pw = ph = 0; + + do { + if (DGifGetRecordType(gif, &rec) == GIF_ERROR) { + err = true; + break; + } + if (rec == EXTENSION_RECORD_TYPE) { + int ext_code; + GifByteType *ext = NULL; + + DGifGetExtension(gif, &ext_code, &ext); + while (ext) { + if (ext_code == GRAPHICS_EXT_FUNC_CODE) { + if (ext[1] & 1) + transp = (int) ext[4]; + else + transp = -1; + + delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]); + disposal = (unsigned int) ext[1] >> 2 & 0x7; + } + ext = NULL; + DGifGetExtensionNext(gif, &ext); + } + } else if (rec == IMAGE_DESC_RECORD_TYPE) { + if (DGifGetImageDesc(gif) == GIF_ERROR) { + err = true; + break; + } + x = gif->Image.Left; + y = gif->Image.Top; + w = gif->Image.Width; + h = gif->Image.Height; + + rows = (GifRowType*) emalloc(h * sizeof(GifRowType)); + for (i = 0; i < h; i++) + rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType)); + if (gif->Image.Interlace) { + for (i = 0; i < 4; i++) { + for (j = intoffset[i]; j < h; j += intjump[i]) + DGifGetLine(gif, rows[j], w); + } + } else { + for (i = 0; i < h; i++) + DGifGetLine(gif, rows[i], w); + } + + ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh); + cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap; + r = cmap->Colors[bg].Red; + g = cmap->Colors[bg].Green; + b = cmap->Colors[bg].Blue; + bgpixel = 0x00ffffff & (r << 16 | g << 8 | b); + + for (i = 0; i < sh; i++) { + for (j = 0; j < sw; j++) { + if (i < y || i >= y + h || j < x || j >= x + w || + rows[i-y][j-x] == transp) + { + if (prev_frame != NULL && (prev_disposal != 2 || + i < py || i >= py + ph || j < px || j >= px + pw)) + { + *ptr = prev_frame[i * sw + j]; + } else { + *ptr = bgpixel; + } + } else { + r = cmap->Colors[rows[i-y][j-x]].Red; + g = cmap->Colors[rows[i-y][j-x]].Green; + b = cmap->Colors[rows[i-y][j-x]].Blue; + *ptr = 0xffu << 24 | r << 16 | g << 8 | b; + } + ptr++; + } + } + + im = imlib_create_image_using_copied_data(sw, sh, data); + + for (i = 0; i < h; i++) + free(rows[i]); + free(rows); + free(data); + + if (im == NULL) { + err = true; + break; + } + + imlib_context_set_image(im); + imlib_image_set_format("gif"); + if (transp >= 0) + imlib_image_set_has_alpha(1); + + if (disposal != 3) + prev_frame = imlib_image_get_data_for_reading_only(); + prev_disposal = disposal; + px = x, py = y, pw = w, ph = h; + + if (img->multi.cnt == img->multi.cap) { + img->multi.cap *= 2; + img->multi.frames = (img_frame_t*) + erealloc(img->multi.frames, + img->multi.cap * sizeof(img_frame_t)); + } + img->multi.frames[img->multi.cnt].im = im; + delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay; + img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; + img->multi.length += img->multi.frames[img->multi.cnt].delay; + img->multi.cnt++; + } + } while (rec != TERMINATE_RECORD_TYPE); + +#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1 + DGifCloseFile(gif, NULL); +#else + DGifCloseFile(gif); +#endif + + if (err && (file->flags & FF_WARN)) + error(0, 0, "%s: Corrupted gif file", file->name); + + if (img->multi.cnt > 1) { + imlib_context_set_image(img->im); + imlib_free_image(); + img->im = img->multi.frames[0].im; + } else if (img->multi.cnt == 1) { + imlib_context_set_image(img->multi.frames[0].im); + imlib_free_image(); + img->multi.cnt = 0; + } + + imlib_context_set_image(img->im); + + return !err; +} +#endif /* HAVE_GIFLIB */ + +Imlib_Image img_open(const fileinfo_t *file) +{ + struct stat st; + Imlib_Image im = NULL; + + if (access(file->path, R_OK) == 0 && + stat(file->path, &st) == 0 && S_ISREG(st.st_mode)) + { + im = imlib_load_image(file->path); + if (im != NULL) { + imlib_context_set_image(im); + if (imlib_image_get_data_for_reading_only() == NULL) { + imlib_free_image(); + im = NULL; + } + } + } + if (im == NULL && (file->flags & FF_WARN)) + error(0, 0, "%s: Error opening image", file->name); + return im; +} + +bool img_load(img_t *img, const fileinfo_t *file) +{ + const char *fmt; + + if ((img->im = img_open(file)) == NULL) + return false; + + imlib_image_set_changes_on_disk(); + +#if HAVE_LIBEXIF + exif_auto_orientate(file); +#endif + + if ((fmt = imlib_image_format()) != NULL) { +#if HAVE_GIFLIB + if (STREQ(fmt, "gif")) + img_load_gif(img, file); +#endif + } + img->w = imlib_image_get_width(); + img->h = imlib_image_get_height(); + img->checkpan = true; + img->dirty = true; + + return true; +} + +CLEANUP void img_close(img_t *img, bool decache) +{ + int i; + + if (img->multi.cnt > 0) { + for (i = 0; i < img->multi.cnt; i++) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_free_image(); + } + img->multi.cnt = 0; + img->im = NULL; + } else if (img->im != NULL) { + imlib_context_set_image(img->im); + if (decache) + imlib_free_image_and_decache(); + else + imlib_free_image(); + img->im = NULL; + } +} + +void img_check_pan(img_t *img, bool moved) +{ + win_t *win; + float w, h, ox, oy; + + win = img->win; + w = img->w * img->zoom; + h = img->h * img->zoom; + ox = img->x; + oy = img->y; + + if (w < win->w) + img->x = (win->w - w) / 2; + else if (img->x > 0) + img->x = 0; + else if (img->x + w < win->w) + img->x = win->w - w; + if (h < win->h) + img->y = (win->h - h) / 2; + else if (img->y > 0) + img->y = 0; + else if (img->y + h < win->h) + img->y = win->h - h; + + if (!moved && (ox != img->x || oy != img->y)) + img->dirty = true; +} + +bool img_fit(img_t *img) +{ + float z, zw, zh; + + if (img->scalemode == SCALE_ZOOM) + return false; + + zw = (float) img->win->w / (float) img->w; + zh = (float) img->win->h / (float) img->h; + + switch (img->scalemode) { + case SCALE_WIDTH: + z = zw; + break; + case SCALE_HEIGHT: + z = zh; + break; + default: + z = MIN(zw, zh); + break; + } + z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max); + + if (zoomdiff(img, z) != 0) { + img->zoom = z; + img->dirty = true; + return true; + } else { + return false; + } +} + +void img_render(img_t *img) +{ + win_t *win; + int sx, sy, sw, sh; + int dx, dy, dw, dh; + Imlib_Image bg; + unsigned long c; + + win = img->win; + img_fit(img); + + if (img->checkpan) { + img_check_pan(img, false); + img->checkpan = false; + } + + if (!img->dirty) + return; + + /* calculate source and destination offsets: + * - part of image drawn on full window, or + * - full image drawn on part of window + */ + if (img->x <= 0) { + sx = -img->x / img->zoom + 0.5; + sw = win->w / img->zoom; + dx = 0; + dw = win->w; + } else { + sx = 0; + sw = img->w; + dx = img->x; + dw = img->w * img->zoom; + } + if (img->y <= 0) { + sy = -img->y / img->zoom + 0.5; + sh = win->h / img->zoom; + dy = 0; + dh = win->h; + } else { + sy = 0; + sh = img->h; + dy = img->y; + dh = img->h * img->zoom; + } + + win_clear(win); + + imlib_context_set_image(img->im); + imlib_context_set_anti_alias(img->aa); + imlib_context_set_drawable(win->buf.pm); + + if (imlib_image_has_alpha()) { + if ((bg = imlib_create_image(dw, dh)) == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + imlib_context_set_image(bg); + imlib_image_set_has_alpha(0); + + if (img->alpha) { + int i, c, r; + DATA32 col[2] = { 0xFF666666, 0xFF999999 }; + DATA32 * data = imlib_image_get_data(); + + for (r = 0; r < dh; r++) { + i = r * dw; + if (r == 0 || r == 8) { + for (c = 0; c < dw; c++) + data[i++] = col[!(c & 8) ^ !r]; + } else { + memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0])); + } + } + imlib_image_put_back_data(data); + } else { + c = win->bg.pixel; + imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF); + imlib_image_fill_rectangle(0, 0, dw, dh); + } + imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh); + imlib_context_set_color_modifier(NULL); + imlib_render_image_on_drawable(dx, dy); + imlib_free_image(); + imlib_context_set_color_modifier(img->cmod); + } else { + imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh); + } + img->dirty = false; +} + +bool img_fit_win(img_t *img, scalemode_t sm) +{ + float oz; + + oz = img->zoom; + img->scalemode = sm; + + if (img_fit(img)) { + img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz; + img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz; + img->checkpan = true; + return true; + } else { + return false; + } +} + +bool img_zoom(img_t *img, float z) +{ + z = MAX(z, zoom_min); + z = MIN(z, zoom_max); + + img->scalemode = SCALE_ZOOM; + + if (zoomdiff(img, z) != 0) { + int x, y; + + win_cursor_pos(img->win, &x, &y); + if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) { + x = img->win->w / 2; + y = img->win->h / 2; + } + img->x = x - (x - img->x) * z / img->zoom; + img->y = y - (y - img->y) * z / img->zoom; + img->zoom = z; + img->checkpan = true; + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_zoom_in(img_t *img) +{ + int i; + float z; + + for (i = 0; i < ARRLEN(zoom_levels); i++) { + z = zoom_levels[i] / 100.0; + if (zoomdiff(img, z) > 0) + return img_zoom(img, z); + } + return false; +} + +bool img_zoom_out(img_t *img) +{ + int i; + float z; + + for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) { + z = zoom_levels[i] / 100.0; + if (zoomdiff(img, z) < 0) + return img_zoom(img, z); + } + return false; +} + +bool img_pos(img_t *img, float x, float y) +{ + float ox, oy; + + ox = img->x; + oy = img->y; + + img->x = x; + img->y = y; + + img_check_pan(img, true); + + if (ox != img->x || oy != img->y) { + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_move(img_t *img, float dx, float dy) +{ + return img_pos(img, img->x + dx, img->y + dy); +} + +bool img_pan(img_t *img, direction_t dir, int d) +{ + /* d < 0: screen-wise + * d = 0: 1/PAN_FRACTION of screen + * d > 0: num of pixels + */ + float x, y; + + if (d > 0) { + x = y = MAX(1, (float) d * img->zoom); + } else { + x = img->win->w / (d < 0 ? 1 : PAN_FRACTION); + y = img->win->h / (d < 0 ? 1 : PAN_FRACTION); + } + + switch (dir) { + case DIR_LEFT: + return img_move(img, x, 0.0); + case DIR_RIGHT: + return img_move(img, -x, 0.0); + case DIR_UP: + return img_move(img, 0.0, y); + case DIR_DOWN: + return img_move(img, 0.0, -y); + } + return false; +} + +bool img_pan_edge(img_t *img, direction_t dir) +{ + float ox, oy; + + ox = img->x; + oy = img->y; + + if (dir & DIR_LEFT) + img->x = 0; + if (dir & DIR_RIGHT) + img->x = img->win->w - img->w * img->zoom; + if (dir & DIR_UP) + img->y = 0; + if (dir & DIR_DOWN) + img->y = img->win->h - img->h * img->zoom; + + img_check_pan(img, true); + + if (ox != img->x || oy != img->y) { + img->dirty = true; + return true; + } else { + return false; + } +} + +void img_rotate(img_t *img, degree_t d) +{ + int i, tmp; + float ox, oy; + + imlib_context_set_image(img->im); + imlib_image_orientate(d); + + for (i = 0; i < img->multi.cnt; i++) { + if (i != img->multi.sel) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_image_orientate(d); + } + } + if (d == DEGREE_90 || d == DEGREE_270) { + ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom; + oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom; + + img->x = oy + (img->win->w - img->win->h) / 2; + img->y = ox + (img->win->h - img->win->w) / 2; + + tmp = img->w; + img->w = img->h; + img->h = tmp; + img->checkpan = true; + } + img->dirty = true; +} + +void img_flip(img_t *img, flipdir_t d) +{ + int i; + void (*imlib_flip_op[3])(void) = { + imlib_image_flip_horizontal, + imlib_image_flip_vertical, + imlib_image_flip_diagonal + }; + + d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1; + + if (d < 0 || d >= ARRLEN(imlib_flip_op)) + return; + + imlib_context_set_image(img->im); + imlib_flip_op[d](); + + for (i = 0; i < img->multi.cnt; i++) { + if (i != img->multi.sel) { + imlib_context_set_image(img->multi.frames[i].im); + imlib_flip_op[d](); + } + } + img->dirty = true; +} + +void img_toggle_antialias(img_t *img) +{ + img->aa = !img->aa; + imlib_context_set_image(img->im); + imlib_context_set_anti_alias(img->aa); + img->dirty = true; +} + +bool img_change_gamma(img_t *img, int d) +{ + /* d < 0: decrease gamma + * d = 0: reset gamma + * d > 0: increase gamma + */ + int gamma; + double range; + + if (d == 0) + gamma = 0; + else + gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE); + + if (img->gamma != gamma) { + imlib_reset_color_modifier(); + if (gamma != 0) { + range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0; + imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE)); + } + img->gamma = gamma; + img->dirty = true; + return true; + } else { + return false; + } +} + +bool img_frame_goto(img_t *img, int n) +{ + if (n < 0 || n >= img->multi.cnt || n == img->multi.sel) + return false; + + img->multi.sel = n; + img->im = img->multi.frames[n].im; + + imlib_context_set_image(img->im); + img->w = imlib_image_get_width(); + img->h = imlib_image_get_height(); + img->checkpan = true; + img->dirty = true; + + return true; +} + +bool img_frame_navigate(img_t *img, int d) +{ + if (img->multi.cnt == 0 || d == 0) + return false; + + d += img->multi.sel; + if (d < 0) + d = 0; + else if (d >= img->multi.cnt) + d = img->multi.cnt - 1; + + return img_frame_goto(img, d); +} + +bool img_frame_animate(img_t *img) +{ + if (img->multi.cnt == 0) + return false; + + if (img->multi.sel + 1 >= img->multi.cnt) + img_frame_goto(img, 0); + else + img_frame_goto(img, img->multi.sel + 1); + img->dirty = true; + return true; +} + diff --git a/suckless/sxiv/main.c b/suckless/sxiv/main.c new file mode 100644 index 00000000..593e1c1a --- /dev/null +++ b/suckless/sxiv/main.c @@ -0,0 +1,951 @@ +/* Copyright 2011-2013 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _MAPPINGS_CONFIG +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + struct timeval when; + bool active; + timeout_f handler; +} timeout_t; + +/* timeout handler functions: */ +void redraw(void); +void reset_cursor(void); +void animate(void); +void slideshow(void); +void clear_resize(void); + +appmode_t mode; +arl_t arl; +img_t img; +tns_t tns; +win_t win; + +fileinfo_t *files; +int filecnt, fileidx; +int alternate; +int markcnt; +int markidx; + +int prefix; +bool extprefix; + +bool resized = false; + +typedef struct { + int err; + char *cmd; +} extcmd_t; + +struct { + extcmd_t f; + int fd; + unsigned int i, lastsep; + pid_t pid; +} info; + +struct { + extcmd_t f; + bool warned; +} keyhandler; + +timeout_t timeouts[] = { + { { 0, 0 }, false, redraw }, + { { 0, 0 }, false, reset_cursor }, + { { 0, 0 }, false, animate }, + { { 0, 0 }, false, slideshow }, + { { 0, 0 }, false, clear_resize }, +}; + +cursor_t imgcursor[3] = { + CURSOR_ARROW, CURSOR_ARROW, CURSOR_ARROW +}; + +void cleanup(void) +{ + img_close(&img, false); + arl_cleanup(&arl); + tns_free(&tns); + win_close(&win); +} + +void check_add_file(char *filename, bool given) +{ + char *path; + + if (*filename == '\0') + return; + + if (access(filename, R_OK) < 0 || + (path = realpath(filename, NULL)) == NULL) + { + if (given) + error(0, errno, "%s", filename); + return; + } + + if (fileidx == filecnt) { + filecnt *= 2; + files = erealloc(files, filecnt * sizeof(*files)); + memset(&files[filecnt/2], 0, filecnt/2 * sizeof(*files)); + } + + files[fileidx].name = estrdup(filename); + files[fileidx].path = path; + if (given) + files[fileidx].flags |= FF_WARN; + fileidx++; +} + +void remove_file(int n, bool manual) +{ + if (n < 0 || n >= filecnt) + return; + + if (filecnt == 1) { + if (!manual) + fprintf(stderr, "sxiv: no more files to display, aborting\n"); + exit(manual ? EXIT_SUCCESS : EXIT_FAILURE); + } + if (files[n].flags & FF_MARK) + markcnt--; + + if (files[n].path != files[n].name) + free((void*) files[n].path); + free((void*) files[n].name); + + if (n + 1 < filecnt) { + if (tns.thumbs != NULL) { + memmove(tns.thumbs + n, tns.thumbs + n + 1, (filecnt - n - 1) * + sizeof(*tns.thumbs)); + memset(tns.thumbs + filecnt - 1, 0, sizeof(*tns.thumbs)); + } + memmove(files + n, files + n + 1, (filecnt - n - 1) * sizeof(*files)); + } + filecnt--; + if (fileidx > n || fileidx == filecnt) + fileidx--; + if (alternate > n || alternate == filecnt) + alternate--; + if (markidx > n || markidx == filecnt) + markidx--; +} + +void set_timeout(timeout_f handler, int time, bool overwrite) +{ + int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + if (!timeouts[i].active || overwrite) { + gettimeofday(&timeouts[i].when, 0); + TV_ADD_MSEC(&timeouts[i].when, time); + timeouts[i].active = true; + } + return; + } + } +} + +void reset_timeout(timeout_f handler) +{ + int i; + + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == handler) { + timeouts[i].active = false; + return; + } + } +} + +bool check_timeouts(struct timeval *t) +{ + int i = 0, tdiff, tmin = -1; + struct timeval now; + + while (i < ARRLEN(timeouts)) { + if (timeouts[i].active) { + gettimeofday(&now, 0); + tdiff = TV_DIFF(&timeouts[i].when, &now); + if (tdiff <= 0) { + timeouts[i].active = false; + if (timeouts[i].handler != NULL) + timeouts[i].handler(); + i = tmin = -1; + } else if (tmin < 0 || tdiff < tmin) { + tmin = tdiff; + } + } + i++; + } + if (tmin > 0 && t != NULL) + TV_SET_MSEC(t, tmin); + return tmin > 0; +} + +void close_info(void) +{ + if (info.fd != -1) { + kill(info.pid, SIGTERM); + close(info.fd); + info.fd = -1; + } +} + +void open_info(void) +{ + int pfd[2]; + char w[12], h[12]; + + if (info.f.err != 0 || info.fd >= 0 || win.bar.h == 0) + return; + win.bar.l.buf[0] = '\0'; + if (pipe(pfd) < 0) + return; + if ((info.pid = fork()) == 0) { + close(pfd[0]); + dup2(pfd[1], 1); + snprintf(w, sizeof(w), "%d", img.w); + snprintf(h, sizeof(h), "%d", img.h); + execl(info.f.cmd, info.f.cmd, files[fileidx].name, w, h, NULL); + error(EXIT_FAILURE, errno, "exec: %s", info.f.cmd); + } + close(pfd[1]); + if (info.pid < 0) { + close(pfd[0]); + } else { + fcntl(pfd[0], F_SETFL, O_NONBLOCK); + info.fd = pfd[0]; + info.i = info.lastsep = 0; + } +} + +void read_info(void) +{ + ssize_t i, n; + char buf[BAR_L_LEN]; + + while (true) { + n = read(info.fd, buf, sizeof(buf)); + if (n < 0 && errno == EAGAIN) + return; + else if (n == 0) + goto end; + for (i = 0; i < n; i++) { + if (buf[i] == '\n') { + if (info.lastsep == 0) { + win.bar.l.buf[info.i++] = ' '; + info.lastsep = 1; + } + } else { + win.bar.l.buf[info.i++] = buf[i]; + info.lastsep = 0; + } + if (info.i + 1 == win.bar.l.size) + goto end; + } + } +end: + info.i -= info.lastsep; + win.bar.l.buf[info.i] = '\0'; + win_draw(&win); + close_info(); +} + +void load_image(int new) +{ + bool prev = new < fileidx; + static int current; + + if (new < 0 || new >= filecnt) + return; + + if (win.xwin != None) + win_set_cursor(&win, CURSOR_WATCH); + reset_timeout(slideshow); + + if (new != current) + alternate = current; + + img_close(&img, false); + while (!img_load(&img, &files[new])) { + remove_file(new, false); + if (new >= filecnt) + new = filecnt - 1; + else if (new > 0 && prev) + new--; + } + files[new].flags &= ~FF_WARN; + fileidx = current = new; + + close_info(); + open_info(); + arl_setup(&arl, files[fileidx].path); + + if (img.multi.cnt > 0 && img.multi.animate) + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + else + reset_timeout(animate); +} + +bool mark_image(int n, bool on) +{ + markidx = n; + if (!!(files[n].flags & FF_MARK) != on) { + files[n].flags ^= FF_MARK; + markcnt += on ? 1 : -1; + if (mode == MODE_THUMB) + tns_mark(&tns, n, on); + return true; + } + return false; +} + +void bar_put(win_bar_t *bar, const char *fmt, ...) +{ + size_t len = bar->size - (bar->p - bar->buf), n; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(bar->p, len, fmt, ap); + bar->p += MIN(len, n); + va_end(ap); +} + +#define BAR_SEP " " + +void update_info(void) +{ + unsigned int i, fn, fw; + const char * mark; + win_bar_t *l = &win.bar.l, *r = &win.bar.r; + + /* update bar contents */ + if (win.bar.h == 0) + return; + for (fw = 0, i = filecnt; i > 0; fw++, i /= 10); + mark = files[fileidx].flags & FF_MARK ? "* " : ""; + l->p = l->buf; + r->p = r->buf; + if (mode == MODE_THUMB) { + if (tns.loadnext < tns.end) + bar_put(l, "Loading... %0*d", fw, tns.loadnext + 1); + else if (tns.initnext < filecnt) + bar_put(l, "Caching... %0*d", fw, tns.initnext + 1); + else + strncpy(l->buf, files[fileidx].name, l->size); + bar_put(r, "%s%0*d/%d", mark, fw, fileidx + 1, filecnt); + } else { + bar_put(r, "%s", mark); + if (img.ss.on) { + if (img.ss.delay % 10 != 0) + bar_put(r, "%2.1fs" BAR_SEP, (float)img.ss.delay / 10); + else + bar_put(r, "%ds" BAR_SEP, img.ss.delay / 10); + } + if (img.gamma != 0) + bar_put(r, "G%+d" BAR_SEP, img.gamma); + bar_put(r, "%3d%%" BAR_SEP, (int) (img.zoom * 100.0)); + if (img.multi.cnt > 0) { + for (fn = 0, i = img.multi.cnt; i > 0; fn++, i /= 10); + bar_put(r, "%0*d/%d" BAR_SEP, fn, img.multi.sel + 1, img.multi.cnt); + } + bar_put(r, "%0*d/%d", fw, fileidx + 1, filecnt); + if (info.f.err) + strncpy(l->buf, files[fileidx].name, l->size); + } +} + +int ptr_third_x(void) +{ + int x, y; + + win_cursor_pos(&win, &x, &y); + return MAX(0, MIN(2, (x / (win.w * 0.33)))); +} + +void redraw(void) +{ + int t; + + if (mode == MODE_IMAGE) { + img_render(&img); + if (img.ss.on) { + t = img.ss.delay * 100; + if (img.multi.cnt > 0 && img.multi.animate) + t = MAX(t, img.multi.length); + set_timeout(slideshow, t, false); + } + } else { + tns_render(&tns); + } + update_info(); + win_draw(&win); + reset_timeout(redraw); + reset_cursor(); +} + +void reset_cursor(void) +{ + int c, i; + cursor_t cursor = CURSOR_NONE; + + if (mode == MODE_IMAGE) { + for (i = 0; i < ARRLEN(timeouts); i++) { + if (timeouts[i].handler == reset_cursor) { + if (timeouts[i].active) { + c = ptr_third_x(); + c = MAX(fileidx > 0 ? 0 : 1, c); + c = MIN(fileidx + 1 < filecnt ? 2 : 1, c); + cursor = imgcursor[c]; + } + break; + } + } + } else { + if (tns.loadnext < tns.end || tns.initnext < filecnt) + cursor = CURSOR_WATCH; + else + cursor = CURSOR_ARROW; + } + win_set_cursor(&win, cursor); +} + +void animate(void) +{ + if (img_frame_animate(&img)) { + redraw(); + set_timeout(animate, img.multi.frames[img.multi.sel].delay, true); + } +} + +void slideshow(void) +{ + load_image(fileidx + 1 < filecnt ? fileidx + 1 : 0); + redraw(); +} + +void clear_resize(void) +{ + resized = false; +} + +Bool is_input_ev(Display *dpy, XEvent *ev, XPointer arg) +{ + return ev->type == ButtonPress || ev->type == KeyPress; +} + +void run_key_handler(const char *key, unsigned int mask) +{ + pid_t pid; + FILE *pfs; + bool marked = mode == MODE_THUMB && markcnt > 0; + bool changed = false; + int f, i, pfd[2]; + int fcnt = marked ? markcnt : 1; + char kstr[32]; + struct stat *oldst, st; + XEvent dump; + + if (keyhandler.f.err != 0) { + if (!keyhandler.warned) { + error(0, keyhandler.f.err, "%s", keyhandler.f.cmd); + keyhandler.warned = true; + } + return; + } + if (key == NULL) + return; + + if (pipe(pfd) < 0) { + error(0, errno, "pipe"); + return; + } + if ((pfs = fdopen(pfd[1], "w")) == NULL) { + error(0, errno, "open pipe"); + close(pfd[0]), close(pfd[1]); + return; + } + oldst = emalloc(fcnt * sizeof(*oldst)); + + close_info(); + strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); + win_draw(&win); + win_set_cursor(&win, CURSOR_WATCH); + + snprintf(kstr, sizeof(kstr), "%s%s%s%s", + mask & ControlMask ? "C-" : "", + mask & Mod1Mask ? "M-" : "", + mask & ShiftMask ? "S-" : "", key); + + if ((pid = fork()) == 0) { + close(pfd[1]); + dup2(pfd[0], 0); + execl(keyhandler.f.cmd, keyhandler.f.cmd, kstr, NULL); + error(EXIT_FAILURE, errno, "exec: %s", keyhandler.f.cmd); + } + close(pfd[0]); + if (pid < 0) { + error(0, errno, "fork"); + fclose(pfs); + goto end; + } + + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + stat(files[i].path, &oldst[f]); + fprintf(pfs, "%s\n", files[i].name); + f++; + } + } + fclose(pfs); + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); + + for (f = i = 0; f < fcnt; i++) { + if ((marked && (files[i].flags & FF_MARK)) || (!marked && i == fileidx)) { + if (stat(files[i].path, &st) != 0 || + memcmp(&oldst[f].st_mtime, &st.st_mtime, sizeof(st.st_mtime)) != 0) + { + if (tns.thumbs != NULL) { + tns_unload(&tns, i); + tns.loadnext = MIN(tns.loadnext, i); + } + changed = true; + } + f++; + } + } + /* drop user input events that occurred while running the key handler */ + while (XCheckIfEvent(win.env.dpy, &dump, is_input_ev, NULL)); + +end: + if (mode == MODE_IMAGE) { + if (changed) { + img_close(&img, true); + load_image(fileidx); + } else { + open_info(); + } + } + free(oldst); + reset_cursor(); + redraw(); +} + +#define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) + +void on_keypress(XKeyEvent *kev) +{ + int i; + unsigned int sh = 0; + KeySym ksym, shksym; + char dummy, key; + bool dirty = false; + + XLookupString(kev, &key, 1, &ksym, NULL); + + if (kev->state & ShiftMask) { + kev->state &= ~ShiftMask; + XLookupString(kev, &dummy, 1, &shksym, NULL); + kev->state |= ShiftMask; + if (ksym != shksym) + sh = ShiftMask; + } + if (IsModifierKey(ksym)) + return; + if (ksym == XK_Escape && MODMASK(kev->state) == 0) { + extprefix = False; + } else if (extprefix) { + run_key_handler(XKeysymToString(ksym), kev->state & ~sh); + extprefix = False; + } else if (key >= '0' && key <= '9') { + /* number prefix for commands */ + prefix = prefix * 10 + (int) (key - '0'); + return; + } else for (i = 0; i < ARRLEN(keys); i++) { + if (keys[i].ksym == ksym && + MODMASK(keys[i].mask | sh) == MODMASK(kev->state) && + keys[i].cmd >= 0 && keys[i].cmd < CMD_COUNT && + (cmds[keys[i].cmd].mode < 0 || cmds[keys[i].cmd].mode == mode)) + { + if (cmds[keys[i].cmd].func(keys[i].arg)) + dirty = true; + } + } + if (dirty) + redraw(); + prefix = 0; +} + +void on_buttonpress(XButtonEvent *bev) +{ + int i, sel; + bool dirty = false; + static Time firstclick; + + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + + for (i = 0; i < ARRLEN(buttons); i++) { + if (buttons[i].button == bev->button && + MODMASK(buttons[i].mask) == MODMASK(bev->state) && + buttons[i].cmd >= 0 && buttons[i].cmd < CMD_COUNT && + (cmds[buttons[i].cmd].mode < 0 || cmds[buttons[i].cmd].mode == mode)) + { + if (cmds[buttons[i].cmd].func(buttons[i].arg)) + dirty = true; + } + } + if (dirty) + redraw(); + } else { + /* thumbnail mode (hard-coded) */ + switch (bev->button) { + case Button1: + if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { + if (sel != fileidx) { + tns_highlight(&tns, fileidx, false); + tns_highlight(&tns, sel, true); + fileidx = sel; + firstclick = bev->time; + redraw(); + } else if (bev->time - firstclick <= TO_DOUBLE_CLICK) { + mode = MODE_IMAGE; + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + load_image(fileidx); + redraw(); + } else { + firstclick = bev->time; + } + } + break; + case Button3: + if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) { + bool on = !(files[sel].flags & FF_MARK); + XEvent e; + + for (;;) { + if (sel >= 0 && mark_image(sel, on)) + redraw(); + XMaskEvent(win.env.dpy, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &e); + if (e.type == ButtonPress || e.type == ButtonRelease) + break; + while (XCheckTypedEvent(win.env.dpy, MotionNotify, &e)); + sel = tns_translate(&tns, e.xbutton.x, e.xbutton.y); + } + } + break; + case Button4: + case Button5: + if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN, + (bev->state & ControlMask) != 0)) + redraw(); + break; + } + } + prefix = 0; +} + +const struct timespec ten_ms = {0, 10000000}; + +void run(void) +{ + int xfd; + fd_set fds; + struct timeval timeout; + bool discard, init_thumb, load_thumb, to_set; + XEvent ev, nextev; + + while (true) { + to_set = check_timeouts(&timeout); + init_thumb = mode == MODE_THUMB && tns.initnext < filecnt; + load_thumb = mode == MODE_THUMB && tns.loadnext < tns.end; + + if ((init_thumb || load_thumb || to_set || info.fd != -1 || + arl.fd != -1) && XPending(win.env.dpy) == 0) + { + if (load_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.loadnext, false, false)) { + remove_file(tns.loadnext, false); + tns.dirty = true; + } + if (tns.loadnext >= tns.end) + redraw(); + } else if (init_thumb) { + set_timeout(redraw, TO_REDRAW_THUMBS, false); + if (!tns_load(&tns, tns.initnext, false, true)) + remove_file(tns.initnext, false); + } else { + xfd = ConnectionNumber(win.env.dpy); + FD_ZERO(&fds); + FD_SET(xfd, &fds); + if (info.fd != -1) { + FD_SET(info.fd, &fds); + xfd = MAX(xfd, info.fd); + } + if (arl.fd != -1) { + FD_SET(arl.fd, &fds); + xfd = MAX(xfd, arl.fd); + } + select(xfd + 1, &fds, 0, 0, to_set ? &timeout : NULL); + if (info.fd != -1 && FD_ISSET(info.fd, &fds)) + read_info(); + if (arl.fd != -1 && FD_ISSET(arl.fd, &fds)) { + if (arl_handle(&arl)) { + /* when too fast, imlib2 can't load the image */ + nanosleep(&ten_ms, NULL); + img_close(&img, true); + load_image(fileidx); + redraw(); + } + } + } + continue; + } + + do { + XNextEvent(win.env.dpy, &ev); + discard = false; + if (XEventsQueued(win.env.dpy, QueuedAlready) > 0) { + XPeekEvent(win.env.dpy, &nextev); + switch (ev.type) { + case ConfigureNotify: + case MotionNotify: + discard = ev.type == nextev.type; + break; + case KeyPress: + discard = (nextev.type == KeyPress || nextev.type == KeyRelease) + && ev.xkey.keycode == nextev.xkey.keycode; + break; + } + } + } while (discard); + + switch (ev.type) { + /* handle events */ + case ButtonPress: + on_buttonpress(&ev.xbutton); + break; + case ClientMessage: + if ((Atom) ev.xclient.data.l[0] == atoms[ATOM_WM_DELETE_WINDOW]) + cmds[g_quit].func(0); + break; + case ConfigureNotify: + if (win_configure(&win, &ev.xconfigure)) { + if (mode == MODE_IMAGE) { + img.dirty = true; + img.checkpan = true; + } else { + tns.dirty = true; + } + if (!resized) { + redraw(); + set_timeout(clear_resize, TO_REDRAW_RESIZE, false); + resized = true; + } else { + set_timeout(redraw, TO_REDRAW_RESIZE, false); + } + } + break; + case KeyPress: + on_keypress(&ev.xkey); + break; + case MotionNotify: + if (mode == MODE_IMAGE) { + set_timeout(reset_cursor, TO_CURSOR_HIDE, true); + reset_cursor(); + } + break; + } + } +} + +int fncmp(const void *a, const void *b) +{ + return strcoll(((fileinfo_t*) a)->name, ((fileinfo_t*) b)->name); +} + +void sigchld(int sig) +{ + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +void setup_signal(int sig, void (*handler)(int sig)) +{ + struct sigaction sa; + + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(sig, &sa, 0) == -1) + error(EXIT_FAILURE, errno, "signal %d", sig); +} + +int main(int argc, char **argv) +{ + int i, start; + size_t n; + ssize_t len; + char *filename; + const char *homedir, *dsuffix = ""; + struct stat fstats; + r_dir_t dir; + + setup_signal(SIGCHLD, sigchld); + setup_signal(SIGPIPE, SIG_IGN); + + setlocale(LC_COLLATE, ""); + + parse_options(argc, argv); + + if (options->clean_cache) { + tns_init(&tns, NULL, NULL, NULL, NULL); + tns_clean_cache(&tns); + exit(EXIT_SUCCESS); + } + + if (options->filecnt == 0 && !options->from_stdin) { + print_usage(); + exit(EXIT_FAILURE); + } + + if (options->recursive || options->from_stdin) + filecnt = 1024; + else + filecnt = options->filecnt; + + files = emalloc(filecnt * sizeof(*files)); + memset(files, 0, filecnt * sizeof(*files)); + fileidx = 0; + + if (options->from_stdin) { + n = 0; + filename = NULL; + while ((len = getline(&filename, &n, stdin)) > 0) { + if (filename[len-1] == '\n') + filename[len-1] = '\0'; + check_add_file(filename, true); + } + free(filename); + } + + for (i = 0; i < options->filecnt; i++) { + filename = options->filenames[i]; + + if (stat(filename, &fstats) < 0) { + error(0, errno, "%s", filename); + continue; + } + if (!S_ISDIR(fstats.st_mode)) { + check_add_file(filename, true); + } else { + if (r_opendir(&dir, filename, options->recursive) < 0) { + error(0, errno, "%s", filename); + continue; + } + start = fileidx; + while ((filename = r_readdir(&dir, true)) != NULL) { + check_add_file(filename, false); + free((void*) filename); + } + r_closedir(&dir); + if (fileidx - start > 1) + qsort(files + start, fileidx - start, sizeof(fileinfo_t), fncmp); + } + } + + if (fileidx == 0) + error(EXIT_FAILURE, 0, "No valid image file given, aborting"); + + filecnt = fileidx; + fileidx = options->startnum < filecnt ? options->startnum : 0; + + for (i = 0; i < ARRLEN(buttons); i++) { + if (buttons[i].cmd == i_cursor_navigate) { + imgcursor[0] = CURSOR_LEFT; + imgcursor[2] = CURSOR_RIGHT; + break; + } + } + + win_init(&win); + img_init(&img, &win); + arl_init(&arl); + + if ((homedir = getenv("XDG_CONFIG_HOME")) == NULL || homedir[0] == '\0') { + homedir = getenv("HOME"); + dsuffix = "/.config"; + } + if (homedir != NULL) { + extcmd_t *cmd[] = { &info.f, &keyhandler.f }; + const char *name[] = { "image-info", "key-handler" }; + + for (i = 0; i < ARRLEN(cmd); i++) { + n = strlen(homedir) + strlen(dsuffix) + strlen(name[i]) + 12; + cmd[i]->cmd = (char*) emalloc(n); + snprintf(cmd[i]->cmd, n, "%s%s/sxiv/exec/%s", homedir, dsuffix, name[i]); + if (access(cmd[i]->cmd, X_OK) != 0) + cmd[i]->err = errno; + } + } else { + error(0, 0, "Exec directory not found"); + } + info.fd = -1; + + if (options->thumb_mode) { + mode = MODE_THUMB; + tns_init(&tns, files, &filecnt, &fileidx, &win); + while (!tns_load(&tns, fileidx, false, false)) + remove_file(fileidx, false); + } else { + mode = MODE_IMAGE; + tns.thumbs = NULL; + load_image(fileidx); + } + win_open(&win); + win_set_cursor(&win, CURSOR_WATCH); + + atexit(cleanup); + + set_timeout(redraw, 25, false); + + run(); + + return 0; +} diff --git a/suckless/sxiv/options.c b/suckless/sxiv/options.c new file mode 100644 index 00000000..de024075 --- /dev/null +++ b/suckless/sxiv/options.c @@ -0,0 +1,180 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _IMAGE_CONFIG +#include "config.h" +#include "version.h" + +#include +#include +#include + +opt_t _options; +const opt_t *options = (const opt_t*) &_options; + +void print_usage(void) +{ + printf("usage: sxiv [-abcfhiopqrtvZ] [-A FRAMERATE] [-e WID] [-G GAMMA] " + "[-g GEOMETRY] [-N NAME] [-n NUM] [-S DELAY] [-s MODE] [-z ZOOM] " + "FILES...\n"); +} + +void print_version(void) +{ + puts("sxiv " VERSION); +} + +void parse_options(int argc, char **argv) +{ + int n, opt; + char *end, *s; + const char *scalemodes = "dfwh"; + + progname = strrchr(argv[0], '/'); + progname = progname ? progname + 1 : argv[0]; + + _options.from_stdin = false; + _options.to_stdout = false; + _options.recursive = false; + _options.startnum = 0; + + _options.scalemode = SCALE_DOWN; + _options.zoom = 1.0; + _options.animate = false; + _options.gamma = 0; + _options.slideshow = 0; + _options.framerate = 0; + + _options.fullscreen = false; + _options.embed = 0; + _options.hide_bar = false; + _options.geometry = NULL; + _options.res_name = NULL; + + _options.quiet = false; + _options.thumb_mode = false; + _options.clean_cache = false; + _options.private_mode = false; + + while ((opt = getopt(argc, argv, "A:abce:fG:g:hin:N:opqrS:s:tvZz:")) != -1) { + switch (opt) { + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 'A': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -A: %s", optarg); + _options.framerate = n; + /* fall through */ + case 'a': + _options.animate = true; + break; + case 'b': + _options.hide_bar = true; + break; + case 'c': + _options.clean_cache = true; + break; + case 'e': + n = strtol(optarg, &end, 0); + if (*end != '\0') + error(EXIT_FAILURE, 0, "Invalid argument for option -e: %s", optarg); + _options.embed = n; + break; + case 'f': + _options.fullscreen = true; + break; + case 'G': + n = strtol(optarg, &end, 0); + if (*end != '\0') + error(EXIT_FAILURE, 0, "Invalid argument for option -G: %s", optarg); + _options.gamma = n; + break; + case 'g': + _options.geometry = optarg; + break; + case 'h': + print_usage(); + exit(EXIT_SUCCESS); + case 'i': + _options.from_stdin = true; + break; + case 'n': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -n: %s", optarg); + _options.startnum = n - 1; + break; + case 'N': + _options.res_name = optarg; + break; + case 'o': + _options.to_stdout = true; + break; + case 'p': + _options.private_mode = true; + break; + case 'q': + _options.quiet = true; + break; + case 'r': + _options.recursive = true; + break; + case 'S': + n = strtof(optarg, &end) * 10; + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -S: %s", optarg); + _options.slideshow = n; + break; + case 's': + s = strchr(scalemodes, optarg[0]); + if (s == NULL || *s == '\0' || strlen(optarg) != 1) + error(EXIT_FAILURE, 0, "Invalid argument for option -s: %s", optarg); + _options.scalemode = s - scalemodes; + break; + case 't': + _options.thumb_mode = true; + break; + case 'v': + print_version(); + exit(EXIT_SUCCESS); + case 'Z': + _options.scalemode = SCALE_ZOOM; + _options.zoom = 1.0; + break; + case 'z': + n = strtol(optarg, &end, 0); + if (*end != '\0' || n <= 0) + error(EXIT_FAILURE, 0, "Invalid argument for option -z: %s", optarg); + _options.scalemode = SCALE_ZOOM; + _options.zoom = (float) n / 100.0; + break; + } + } + + _options.filenames = argv + optind; + _options.filecnt = argc - optind; + + if (_options.filecnt == 1 && STREQ(_options.filenames[0], "-")) { + _options.filenames++; + _options.filecnt--; + _options.from_stdin = true; + } +} diff --git a/suckless/sxiv/sxiv b/suckless/sxiv/sxiv new file mode 100755 index 0000000000000000000000000000000000000000..85b84c1c32a52ef82e7fe7a4e42fe540dd6804da GIT binary patch literal 106944 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 literal 0 HcmV?d00001 diff --git a/suckless/sxiv/sxiv.1 b/suckless/sxiv/sxiv.1 new file mode 100644 index 00000000..eff3d666 --- /dev/null +++ b/suckless/sxiv/sxiv.1 @@ -0,0 +1,448 @@ +.TH SXIV 1 sxiv\-VERSION +.SH NAME +sxiv \- Simple X Image Viewer +.SH SYNOPSIS +.B sxiv +.RB [ \-abcfhiopqrtvZ ] +.RB [ \-A +.IR FRAMERATE ] +.RB [ \-e +.IR WID ] +.RB [ \-G +.IR GAMMA ] +.RB [ \-g +.IR GEOMETRY ] +.RB [ \-N +.IR NAME ] +.RB [ \-n +.IR NUM ] +.RB [ \-S +.IR DELAY ] +.RB [ \-s +.IR MODE ] +.RB [ \-z +.IR ZOOM ] +.IR FILE ... +.SH DESCRIPTION +sxiv is a simple image viewer for X. +.P +It has two modes of operation: image and thumbnail mode. The default is image +mode, in which only the current image is shown. In thumbnail mode a grid of +small previews is displayed, making it easy to choose an image to open. +.P +Please note, that the fullscreen mode requires an EWMH/NetWM compliant window +manager. +.SH OPTIONS +.TP +.BI "\-A " FRAMERATE +Play animations with a constant frame rate set to +.IR FRAMERATE . +.TP +.B \-a +Play animations of multi-frame images. +.TP +.B \-b +Do not show info bar on bottom of window. +.TP +.B \-c +Remove all orphaned cache files from the thumbnail cache directory and exit. +.TP +.BI "\-e " WID +Embed sxiv's window into window whose ID is +.IR WID . +.TP +.B \-f +Start in fullscreen mode. +.TP +.BI "\-G " GAMMA +Set image gamma to GAMMA (-32..32). +.TP +.BI "\-g " GEOMETRY +Set window position and size. See section GEOMETRY SPECIFICATIONS of X(7) for +more information on GEOMETRY argument. +.TP +.BI "\-N " NAME +Set the resource name of sxiv's X window to NAME. +.TP +.BI "\-n " NUM +Start at picture number NUM. +.TP +.B \-h +Print brief usage information to standard output and exit. +.TP +.B \-i +Read names of files to open from standard input. Also done if FILE is `-'. +.TP +.B \-o +Write list of all marked files to standard output when quitting. In combination +with +.B \-i +sxiv can be used as a visual filter/pipe. +.TP +.B \-p +Enable private mode, in which sxiv does not write any cache or temporary files. +.TP +.B \-q +Be quiet, disable warnings to standard error stream. +.TP +.B \-r +Search the given directories recursively for images to view. +.TP +.BI "\-S " DELAY +Start in slideshow mode. Set the delay between images to +.I DELAY +seconds. +.I DELAY +may be a floating point number. +.TP +.BI "\-s " MODE +Set scale mode according to MODE character. Supported modes are: [d]own, +[f]it, [w]idth, [h]eight. +.TP +.B \-t +Start in thumbnail mode. +.TP +.B \-v +Print version information to standard output and exit. +.TP +.B \-Z +The same as `\-z 100'. +.TP +.BI "\-z " ZOOM +Set zoom level to ZOOM percent. +.SH KEYBOARD COMMANDS +.SS General +The following keyboard commands are available in both image and thumbnail mode: +.TP +.BR 0 \- 9 +Prefix the next command with a number (denoted via +.IR count ). +.TP +.B q +Quit sxiv. +.TP +.B Return +Switch to thumbnail mode / open selected image in image mode. +.TP +.B f +Toggle fullscreen mode. +.TP +.B b +Toggle visibility of info bar on bottom of window. +.TP +.B Ctrl-x +Send the next key to the external key-handler. See section EXTERNAL KEY HANDLER +for more information. +.TP +.B g +Go to the first image. +.TP +.B G +Go to the last image, or image number +.IR count . +.TP +.B r +Reload image. +.TP +.B D +Remove current image from file list and go to next image. +.TP +.BR Ctrl-h ", " Ctrl-Left +Scroll left one screen width. +.TP +.BR Ctrl-j ", " Ctrl-Down +Scroll down one screen height. +.TP +.BR Ctrl-k ", " Ctrl-Up +Scroll up one screen height. +.TP +.BR Ctrl-l ", " Ctrl-Right +Scroll right one screen width. +.TP +.BR + +Zoom in. +.TP +.B \- +Zoom out. +.TP +.B m +Mark/unmark the current image. +.TP +.B M +Reverse all image marks. +.TP +.B Ctrl-M +Repeat last mark action on all images from the last marked/unmarked up to the +current one. +.TP +.B Ctrl-m +Remove all image marks. +.TP +.B N +Go +.I count +marked images forward. +.TP +.B P +Go +.I count +marked images backward. +.TP +.B { +Decrease gamma correction by +.I count +steps. +.TP +.B } +Increase gamma correction by +.I count +steps. +.TP +.B Ctrl-g +Reset gamma correction. +.SS Thumbnail mode +The following keyboard commands are only available in thumbnail mode: +.TP +.BR h ", " Left +Move selection left +.I count +times. +.TP +.BR j ", " Down +Move selection down +.I count +times. +.TP +.BR k ", " Up +Move selection up +.I count +times. +.TP +.BR l ", " Right +Move selection right +.I count +times. +.TP +.B R +Reload all thumbnails. +.SS Image mode +The following keyboard commands are only available in image mode: +.TP +Navigate image list: +.TP +.BR n ", " Space +Go +.I count +images forward. +.TP +.BR p ", " Backspace +Go +.I count +images backward. +.TP +.B [ +Go +.I count +* 10 images backward. +.TP +.B ] +Go +.I count +* 10 images forward. +.TP +Handle multi-frame images: +.TP +.B Ctrl-n +Go +.I count +frames of a multi-frame image forward. +.TP +.B Ctrl-p +Go +.I count +frames of a multi-frame image backward. +.TP +.B Ctrl-Space +Play/stop animations of multi-frame images. +.TP +Panning: +.TP +.BR h ", " Left +Scroll image 1/5 of window width or +.I count +pixel left. +.TP +.BR j ", " Down +Scroll image 1/5 of window height or +.I count +pixel down. +.TP +.BR k ", " Up +Scroll image 1/5 of window height or +.I count +pixel up. +.TP +.BR l ", " Right +Scroll image 1/5 of window width or +.I count +pixel right. +.TP +.B H +Scroll to left image edge. +.TP +.B J +Scroll to bottom image edge. +.TP +.B K +Scroll to top image edge. +.TP +.B L +Scroll to right image edge. +.TP +Zooming: +.TP +.B = +Set zoom level to 100%, or +.IR count %. +.TP +.B w +Set zoom level to 100%, but fit large images into window. +.TP +.B W +Fit image to window. +.TP +.B e +Fit image to window width. +.TP +.B E +Fit image to window height. +.TP +Rotation: +.TP +.B < +Rotate image counter-clockwise by 90 degrees. +.TP +.B > +Rotate image clockwise by 90 degrees. +.TP +.B ? +Rotate image by 180 degrees. +.TP +Flipping: +.TP +.B | +Flip image horizontally. +.TP +.B _ +Flip image vertically. +.TP +Miscellaneous: +.TP +.B a +Toggle anti-aliasing. +.TP +.B A +Toggle visibility of alpha-channel, i.e. image transparency. +.TP +.B s +Toggle slideshow mode and/or set the delay between images to +.I count +seconds. +.SH MOUSE COMMANDS +The following mouse mappings are available in image mode: +.TP +General: +.TP +.B Button3 +Switch to thumbnail mode. +.TP +Navigate image list: +.TP +.B Button1 +Go to the next image if the mouse cursor is in the right part of the window or +to the previous image if it is in the left part. +.TP +Panning: +.TP +.B Button2 +Pan the image according to the mouse cursor position in the window while +keeping this button pressed down. +.TP +Zooming: +.TP +.B ScrollUp +Zoom in. +.TP +.B ScrollDown +Zoom out. +.SH CONFIGURATION +The following X resources are supported: +.TP +.B background +Color of the window background and bar foreground +.TP +.B foreground +Color of the window foreground and bar background +.TP +.B font +Name of Xft bar font +.TP +Please see xrdb(1) on how to change them. +.SH STATUS BAR +The information displayed on the left side of the status bar can be replaced +with the output of a user-provided script, which is called by sxiv whenever an +image gets loaded. The path of this script is +.I $XDG_CONFIG_HOME/sxiv/exec/image-info +and the arguments given to it are: 1) path to image file, 2) image width, +3) image height. +.P +There is also an example script installed together with sxiv as +.IR PREFIX/share/sxiv/exec/image-info . +.SH EXTERNAL KEY HANDLER +Additional external keyboard commands can be defined using a handler program +located in +.IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . +The handler is invoked by pressing +.BR Ctrl-x . +The next key combo is passed as its first argument. Passed via stdin are the +images to act upon, one path per line: all marked images, if in thumbnail mode +and at least one image has been marked, otherwise the current image. +sxiv(1) will block until the handler terminates. It then checks which images +have been modified and reloads them. + +The key combo argument has the following form: "[C-][M-][S-]KEY", +where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X +keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. + +There is also an example script installed together with sxiv as +.IR PREFIX/share/sxiv/exec/key-handler . +.SH THUMBNAIL CACHING +sxiv stores all thumbnails under +.IR $XDG_CACHE_HOME/sxiv/ . +.P +Use the command line option +.I \-c +to remove all orphaned cache files. Additionally, run the following command +afterwards inside the cache directory to remove empty subdirectories: +.P +.RS +find . \-depth \-type d \-empty ! \-name '.' \-exec rmdir {} \\; +.RE +.SH AUTHOR +.EX +Bert Muennich +.EE +.SH CONTRIBUTORS +.EX +Bastien Dejean +Dave Reisner +Fung SzeTat +Max Voit +.EE +.SH HOMEPAGE +.EX +https://github.com/muennich/sxiv +.EE +.SH SEE ALSO +.BR X (7), +.BR xrdb (1) diff --git a/suckless/sxiv/sxiv.desktop b/suckless/sxiv/sxiv.desktop new file mode 100644 index 00000000..2940b326 --- /dev/null +++ b/suckless/sxiv/sxiv.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=sxiv +GenericName=Image Viewer +Exec=sxiv %F +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; +NoDisplay=true +Icon=sxiv diff --git a/suckless/sxiv/sxiv.h b/suckless/sxiv/sxiv.h new file mode 100644 index 00000000..707eba71 --- /dev/null +++ b/suckless/sxiv/sxiv.h @@ -0,0 +1,448 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#ifndef SXIV_H +#define SXIV_H + +#include +#include +#include +#include +#include +#include +#include + +/* + * Annotation for functions called in cleanup(). + * These functions are not allowed to call error(!0, ...) or exit(). + */ +#define CLEANUP + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) + +#define STREQ(s1,s2) (strcmp((s1), (s2)) == 0) + +#define TV_DIFF(t1,t2) (((t1)->tv_sec - (t2)->tv_sec ) * 1000 + \ + ((t1)->tv_usec - (t2)->tv_usec) / 1000) + +#define TV_SET_MSEC(tv,t) { \ + (tv)->tv_sec = (t) / 1000; \ + (tv)->tv_usec = (t) % 1000 * 1000; \ +} + +#define TV_ADD_MSEC(tv,t) { \ + (tv)->tv_sec += (t) / 1000; \ + (tv)->tv_usec += (t) % 1000 * 1000; \ +} + +typedef enum { + BO_BIG_ENDIAN, + BO_LITTLE_ENDIAN +} byteorder_t; + +typedef enum { + MODE_IMAGE, + MODE_THUMB +} appmode_t; + +typedef enum { + DIR_LEFT = 1, + DIR_RIGHT = 2, + DIR_UP = 4, + DIR_DOWN = 8 +} direction_t; + +typedef enum { + DEGREE_90 = 1, + DEGREE_180 = 2, + DEGREE_270 = 3 +} degree_t; + +typedef enum { + FLIP_HORIZONTAL = 1, + FLIP_VERTICAL = 2 +} flipdir_t; + +typedef enum { + SCALE_DOWN, + SCALE_FIT, + SCALE_WIDTH, + SCALE_HEIGHT, + SCALE_ZOOM +} scalemode_t; + +typedef enum { + DRAG_RELATIVE, + DRAG_ABSOLUTE +} dragmode_t; + +typedef enum { + CURSOR_ARROW, + CURSOR_DRAG, + CURSOR_WATCH, + CURSOR_LEFT, + CURSOR_RIGHT, + CURSOR_NONE, + + CURSOR_COUNT +} cursor_t; + +typedef enum { + FF_WARN = 1, + FF_MARK = 2, + FF_TN_INIT = 4 +} fileflags_t; + +typedef struct { + const char *name; /* as given by user */ + const char *path; /* always absolute */ + fileflags_t flags; +} fileinfo_t; + +/* timeouts in milliseconds: */ +enum { + TO_REDRAW_RESIZE = 75, + TO_REDRAW_THUMBS = 200, + TO_CURSOR_HIDE = 1200, + TO_DOUBLE_CLICK = 300 +}; + +typedef void (*timeout_f)(void); + +typedef struct arl arl_t; +typedef struct img img_t; +typedef struct opt opt_t; +typedef struct tns tns_t; +typedef struct win win_t; + + +/* autoreload.c */ + +struct arl { + int fd; + int wd_dir; + int wd_file; + char *filename; +}; + +void arl_init(arl_t*); +void arl_cleanup(arl_t*); +void arl_setup(arl_t*, const char* /* result of realpath(3) */); +bool arl_handle(arl_t*); + + +/* commands.c */ + +typedef int arg_t; +typedef bool (*cmd_f)(arg_t); + +#define G_CMD(c) g_##c, +#define I_CMD(c) i_##c, +#define T_CMD(c) t_##c, + +typedef enum { +#include "commands.lst" + CMD_COUNT +} cmd_id_t; + +typedef struct { + int mode; + cmd_f func; +} cmd_t; + +typedef struct { + unsigned int mask; + KeySym ksym; + cmd_id_t cmd; + arg_t arg; +} keymap_t; + +typedef struct { + unsigned int mask; + unsigned int button; + cmd_id_t cmd; + arg_t arg; +} button_t; + +extern const cmd_t cmds[CMD_COUNT]; + + +/* image.c */ + +typedef struct { + Imlib_Image im; + unsigned int delay; +} img_frame_t; + +typedef struct { + img_frame_t *frames; + int cap; + int cnt; + int sel; + bool animate; + int framedelay; + int length; +} multi_img_t; + +struct img { + Imlib_Image im; + int w; + int h; + + win_t *win; + float x; + float y; + + scalemode_t scalemode; + float zoom; + + bool checkpan; + bool dirty; + bool aa; + bool alpha; + + Imlib_Color_Modifier cmod; + int gamma; + + struct { + bool on; + int delay; + } ss; + + multi_img_t multi; +}; + +void img_init(img_t*, win_t*); +bool img_load(img_t*, const fileinfo_t*); +CLEANUP void img_close(img_t*, bool); +void img_render(img_t*); +bool img_fit_win(img_t*, scalemode_t); +bool img_zoom(img_t*, float); +bool img_zoom_in(img_t*); +bool img_zoom_out(img_t*); +bool img_pos(img_t*, float, float); +bool img_move(img_t*, float, float); +bool img_pan(img_t*, direction_t, int); +bool img_pan_edge(img_t*, direction_t); +void img_rotate(img_t*, degree_t); +void img_flip(img_t*, flipdir_t); +void img_toggle_antialias(img_t*); +bool img_change_gamma(img_t*, int); +bool img_frame_navigate(img_t*, int); +bool img_frame_animate(img_t*); + + +/* options.c */ + +struct opt { + /* file list: */ + char **filenames; + bool from_stdin; + bool to_stdout; + bool recursive; + int filecnt; + int startnum; + + /* image: */ + scalemode_t scalemode; + float zoom; + bool animate; + int gamma; + int slideshow; + int framerate; + + /* window: */ + bool fullscreen; + bool hide_bar; + long embed; + char *geometry; + char *res_name; + + /* misc flags: */ + bool quiet; + bool thumb_mode; + bool clean_cache; + bool private_mode; +}; + +extern const opt_t *options; + +void print_usage(void); +void print_version(void); +void parse_options(int, char**); + + +/* thumbs.c */ + +typedef struct { + Imlib_Image im; + int w; + int h; + int x; + int y; +} thumb_t; + +struct tns { + fileinfo_t *files; + thumb_t *thumbs; + const int *cnt; + int *sel; + int initnext; + int loadnext; + int first, end; + int r_first, r_end; + + win_t *win; + int x; + int y; + int cols; + int rows; + int zl; + int bw; + int dim; + + bool dirty; +}; + +void tns_clean_cache(tns_t*); +void tns_init(tns_t*, fileinfo_t*, const int*, int*, win_t*); +CLEANUP void tns_free(tns_t*); +bool tns_load(tns_t*, int, bool, bool); +void tns_unload(tns_t*, int); +void tns_render(tns_t*); +void tns_mark(tns_t*, int, bool); +void tns_highlight(tns_t*, int, bool); +bool tns_move_selection(tns_t*, direction_t, int); +bool tns_scroll(tns_t*, direction_t, bool); +bool tns_zoom(tns_t*, int); +int tns_translate(tns_t*, int, int); + + +/* util.c */ + +#include + +typedef struct { + DIR *dir; + char *name; + int d; + bool recursive; + + char **stack; + int stcap; + int stlen; +} r_dir_t; + +extern const char *progname; + +void* emalloc(size_t); +void* erealloc(void*, size_t); +char* estrdup(const char*); +void error(int, int, const char*, ...); +void size_readable(float*, const char**); +int r_opendir(r_dir_t*, const char*, bool); +int r_closedir(r_dir_t*); +char* r_readdir(r_dir_t*, bool); +int r_mkdir(char*); + + +/* window.c */ + +#include +#include + +enum { + BAR_L_LEN = 512, + BAR_R_LEN = 64 +}; + +enum { + ATOM_WM_DELETE_WINDOW, + ATOM__NET_WM_NAME, + ATOM__NET_WM_ICON_NAME, + ATOM__NET_WM_ICON, + ATOM__NET_WM_STATE, + ATOM__NET_WM_STATE_FULLSCREEN, + ATOM_COUNT +}; + +typedef struct { + Display *dpy; + int scr; + int scrw, scrh; + Visual *vis; + Colormap cmap; + int depth; +} win_env_t; + +typedef struct { + size_t size; + char *p; + char *buf; +} win_bar_t; + +struct win { + Window xwin; + win_env_t env; + + XftColor bg; + XftColor fg; + + int x; + int y; + unsigned int w; + unsigned int h; /* = win height - bar height */ + unsigned int bw; + + struct { + int w; + int h; + Pixmap pm; + } buf; + + struct { + unsigned int h; + win_bar_t l; + win_bar_t r; + } bar; +}; + +extern Atom atoms[ATOM_COUNT]; + +void win_init(win_t*); +void win_open(win_t*); +CLEANUP void win_close(win_t*); +bool win_configure(win_t*, XConfigureEvent*); +void win_toggle_fullscreen(win_t*); +void win_toggle_bar(win_t*); +void win_clear(win_t*); +void win_draw(win_t*); +void win_draw_rect(win_t*, int, int, int, int, bool, int, unsigned long); +void win_set_title(win_t*, const char*); +void win_set_cursor(win_t*, cursor_t); +void win_cursor_pos(win_t*, int*, int*); + +#endif /* SXIV_H */ + diff --git a/suckless/sxiv/thumbs.c b/suckless/sxiv/thumbs.c new file mode 100644 index 00000000..36874d63 --- /dev/null +++ b/suckless/sxiv/thumbs.c @@ -0,0 +1,594 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _THUMBS_CONFIG +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_LIBEXIF +#include +void exif_auto_orientate(const fileinfo_t*); +#endif +Imlib_Image img_open(const fileinfo_t*); + +static char *cache_dir; + +char* tns_cache_filepath(const char *filepath) +{ + size_t len; + char *cfile = NULL; + + if (*filepath != '/') + return NULL; + + if (strncmp(filepath, cache_dir, strlen(cache_dir)) != 0) { + /* don't cache images inside the cache directory! */ + len = strlen(cache_dir) + strlen(filepath) + 2; + cfile = (char*) emalloc(len); + snprintf(cfile, len, "%s/%s", cache_dir, filepath + 1); + } + return cfile; +} + +Imlib_Image tns_cache_load(const char *filepath, bool *outdated) +{ + char *cfile; + struct stat cstats, fstats; + Imlib_Image im = NULL; + + if (stat(filepath, &fstats) < 0) + return NULL; + + if ((cfile = tns_cache_filepath(filepath)) != NULL) { + if (stat(cfile, &cstats) == 0) { + if (cstats.st_mtime == fstats.st_mtime) + im = imlib_load_image(cfile); + else + *outdated = true; + } + free(cfile); + } + return im; +} + +void tns_cache_write(Imlib_Image im, const char *filepath, bool force) +{ + char *cfile, *dirend; + struct stat cstats, fstats; + struct utimbuf times; + Imlib_Load_Error err; + + if (options->private_mode) + return; + + if (stat(filepath, &fstats) < 0) + return; + + if ((cfile = tns_cache_filepath(filepath)) != NULL) { + if (force || stat(cfile, &cstats) < 0 || + cstats.st_mtime != fstats.st_mtime) + { + if ((dirend = strrchr(cfile, '/')) != NULL) { + *dirend = '\0'; + if (r_mkdir(cfile) == -1) { + error(0, errno, "%s", cfile); + goto end; + } + *dirend = '/'; + } + imlib_context_set_image(im); + if (imlib_image_has_alpha()) { + imlib_image_set_format("png"); + } else { + imlib_image_set_format("jpg"); + imlib_image_attach_data_value("quality", NULL, 90, NULL); + } + imlib_save_image_with_error_return(cfile, &err); + if (err) + goto end; + times.actime = fstats.st_atime; + times.modtime = fstats.st_mtime; + utime(cfile, ×); + } +end: + free(cfile); + } +} + +void tns_clean_cache(tns_t *tns) +{ + int dirlen; + char *cfile, *filename; + r_dir_t dir; + + if (r_opendir(&dir, cache_dir, true) < 0) { + error(0, errno, "%s", cache_dir); + return; + } + + dirlen = strlen(cache_dir); + + while ((cfile = r_readdir(&dir, false)) != NULL) { + filename = cfile + dirlen; + if (access(filename, F_OK) < 0) { + if (unlink(cfile) < 0) + error(0, errno, "%s", cfile); + } + free(cfile); + } + r_closedir(&dir); +} + + +void tns_init(tns_t *tns, fileinfo_t *files, const int *cnt, int *sel, + win_t *win) +{ + int len; + const char *homedir, *dsuffix = ""; + + if (cnt != NULL && *cnt > 0) { + tns->thumbs = (thumb_t*) emalloc(*cnt * sizeof(thumb_t)); + memset(tns->thumbs, 0, *cnt * sizeof(thumb_t)); + } else { + tns->thumbs = NULL; + } + tns->files = files; + tns->cnt = cnt; + tns->initnext = tns->loadnext = 0; + tns->first = tns->end = tns->r_first = tns->r_end = 0; + tns->sel = sel; + tns->win = win; + tns->dirty = false; + + tns->zl = THUMB_SIZE; + tns_zoom(tns, 0); + + if ((homedir = getenv("XDG_CACHE_HOME")) == NULL || homedir[0] == '\0') { + homedir = getenv("HOME"); + dsuffix = "/.cache"; + } + if (homedir != NULL) { + free(cache_dir); + len = strlen(homedir) + strlen(dsuffix) + 6; + cache_dir = (char*) emalloc(len); + snprintf(cache_dir, len, "%s%s/sxiv", homedir, dsuffix); + } else { + error(0, 0, "Cache directory not found"); + } +} + +CLEANUP void tns_free(tns_t *tns) +{ + int i; + + if (tns->thumbs != NULL) { + for (i = 0; i < *tns->cnt; i++) { + if (tns->thumbs[i].im != NULL) { + imlib_context_set_image(tns->thumbs[i].im); + imlib_free_image(); + } + } + free(tns->thumbs); + tns->thumbs = NULL; + } + + free(cache_dir); + cache_dir = NULL; +} + +Imlib_Image tns_scale_down(Imlib_Image im, int dim) +{ + int w, h; + float z, zw, zh; + + imlib_context_set_image(im); + w = imlib_image_get_width(); + h = imlib_image_get_height(); + zw = (float) dim / (float) w; + zh = (float) dim / (float) h; + z = MIN(zw, zh); + z = MIN(z, 1.0); + + if (z < 1.0) { + imlib_context_set_anti_alias(1); + im = imlib_create_cropped_scaled_image(0, 0, w, h, + MAX(z * w, 1), MAX(z * h, 1)); + if (im == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + imlib_free_image_and_decache(); + } + return im; +} + +bool tns_load(tns_t *tns, int n, bool force, bool cache_only) +{ + int maxwh = thumb_sizes[ARRLEN(thumb_sizes)-1]; + bool cache_hit = false; + char *cfile; + thumb_t *t; + fileinfo_t *file; + Imlib_Image im = NULL; + + if (n < 0 || n >= *tns->cnt) + return false; + file = &tns->files[n]; + if (file->name == NULL || file->path == NULL) + return false; + + t = &tns->thumbs[n]; + + if (t->im != NULL) { + imlib_context_set_image(t->im); + imlib_free_image(); + t->im = NULL; + } + + if (!force) { + if ((im = tns_cache_load(file->path, &force)) != NULL) { + imlib_context_set_image(im); + if (imlib_image_get_width() < maxwh && + imlib_image_get_height() < maxwh) + { + if ((cfile = tns_cache_filepath(file->path)) != NULL) { + unlink(cfile); + free(cfile); + } + imlib_free_image_and_decache(); + im = NULL; + } else { + cache_hit = true; + } +#if HAVE_LIBEXIF + } else if (!force && !options->private_mode) { + int pw = 0, ph = 0, w, h, x = 0, y = 0; + bool err; + float zw, zh; + ExifData *ed; + ExifEntry *entry; + ExifContent *ifd; + ExifByteOrder byte_order; + int tmpfd; + char tmppath[] = "/tmp/sxiv-XXXXXX"; + Imlib_Image tmpim; + + if ((ed = exif_data_new_from_file(file->path)) != NULL) { + if (ed->data != NULL && ed->size > 0 && + (tmpfd = mkstemp(tmppath)) >= 0) + { + err = write(tmpfd, ed->data, ed->size) != ed->size; + close(tmpfd); + + if (!err && (tmpim = imlib_load_image(tmppath)) != NULL) { + byte_order = exif_data_get_byte_order(ed); + ifd = ed->ifd[EXIF_IFD_EXIF]; + entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_X_DIMENSION); + if (entry != NULL) + pw = exif_get_long(entry->data, byte_order); + entry = exif_content_get_entry(ifd, EXIF_TAG_PIXEL_Y_DIMENSION); + if (entry != NULL) + ph = exif_get_long(entry->data, byte_order); + + imlib_context_set_image(tmpim); + w = imlib_image_get_width(); + h = imlib_image_get_height(); + + if (pw > w && ph > h && (pw - ph >= 0) == (w - h >= 0)) { + zw = (float) pw / (float) w; + zh = (float) ph / (float) h; + if (zw < zh) { + pw /= zh; + x = (w - pw) / 2; + w = pw; + } else if (zw > zh) { + ph /= zw; + y = (h - ph) / 2; + h = ph; + } + } + if (w >= maxwh || h >= maxwh) { + if ((im = imlib_create_cropped_image(x, y, w, h)) == NULL) + error(EXIT_FAILURE, ENOMEM, NULL); + } + imlib_free_image_and_decache(); + } + unlink(tmppath); + } + exif_data_unref(ed); + } +#endif + } + } + + if (im == NULL) { + if ((im = img_open(file)) == NULL) + return false; + } + imlib_context_set_image(im); + + if (!cache_hit) { +#if HAVE_LIBEXIF + exif_auto_orientate(file); +#endif + im = tns_scale_down(im, maxwh); + imlib_context_set_image(im); + if (imlib_image_get_width() == maxwh || imlib_image_get_height() == maxwh) + tns_cache_write(im, file->path, true); + } + + if (cache_only) { + imlib_free_image_and_decache(); + } else { + t->im = tns_scale_down(im, thumb_sizes[tns->zl]); + imlib_context_set_image(t->im); + t->w = imlib_image_get_width(); + t->h = imlib_image_get_height(); + tns->dirty = true; + } + file->flags |= FF_TN_INIT; + + if (n == tns->initnext) + while (++tns->initnext < *tns->cnt && ((++file)->flags & FF_TN_INIT)); + if (n == tns->loadnext && !cache_only) + while (++tns->loadnext < tns->end && (++t)->im != NULL); + + return true; +} + +void tns_unload(tns_t *tns, int n) +{ + thumb_t *t; + + if (n < 0 || n >= *tns->cnt) + return; + + t = &tns->thumbs[n]; + + if (t->im != NULL) { + imlib_context_set_image(t->im); + imlib_free_image(); + t->im = NULL; + } +} + +void tns_check_view(tns_t *tns, bool scrolled) +{ + int r; + + if (tns == NULL) + return; + + tns->first -= tns->first % tns->cols; + r = *tns->sel % tns->cols; + + if (scrolled) { + /* move selection into visible area */ + if (*tns->sel >= tns->first + tns->cols * tns->rows) + *tns->sel = tns->first + r + tns->cols * (tns->rows - 1); + else if (*tns->sel < tns->first) + *tns->sel = tns->first + r; + } else { + /* scroll to selection */ + if (tns->first + tns->cols * tns->rows <= *tns->sel) { + tns->first = *tns->sel - r - tns->cols * (tns->rows - 1); + tns->dirty = true; + } else if (tns->first > *tns->sel) { + tns->first = *tns->sel - r; + tns->dirty = true; + } + } +} + +void tns_render(tns_t *tns) +{ + thumb_t *t; + win_t *win; + int i, cnt, r, x, y; + + if (!tns->dirty) + return; + + win = tns->win; + win_clear(win); + imlib_context_set_drawable(win->buf.pm); + + tns->cols = MAX(1, win->w / tns->dim); + tns->rows = MAX(1, win->h / tns->dim); + + if (*tns->cnt < tns->cols * tns->rows) { + tns->first = 0; + cnt = *tns->cnt; + } else { + tns_check_view(tns, false); + cnt = tns->cols * tns->rows; + if ((r = tns->first + cnt - *tns->cnt) >= tns->cols) + tns->first -= r - r % tns->cols; + if (r > 0) + cnt -= r % tns->cols; + } + r = cnt % tns->cols ? 1 : 0; + tns->x = x = (win->w - MIN(cnt, tns->cols) * tns->dim) / 2 + tns->bw + 3; + tns->y = y = (win->h - (cnt / tns->cols + r) * tns->dim) / 2 + tns->bw + 3; + tns->loadnext = *tns->cnt; + tns->end = tns->first + cnt; + + for (i = tns->r_first; i < tns->r_end; i++) { + if ((i < tns->first || i >= tns->end) && tns->thumbs[i].im != NULL) + tns_unload(tns, i); + } + tns->r_first = tns->first; + tns->r_end = tns->end; + + for (i = tns->first; i < tns->end; i++) { + t = &tns->thumbs[i]; + if (t->im != NULL) { + t->x = x + (thumb_sizes[tns->zl] - t->w) / 2; + t->y = y + (thumb_sizes[tns->zl] - t->h) / 2; + imlib_context_set_image(t->im); + imlib_render_image_on_drawable_at_size(t->x, t->y, t->w, t->h); + if (tns->files[i].flags & FF_MARK) + tns_mark(tns, i, true); + } else { + tns->loadnext = MIN(tns->loadnext, i); + } + if ((i + 1) % tns->cols == 0) { + x = tns->x; + y += tns->dim; + } else { + x += tns->dim; + } + } + tns->dirty = false; + tns_highlight(tns, *tns->sel, true); +} + +void tns_mark(tns_t *tns, int n, bool mark) +{ + if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { + win_t *win = tns->win; + thumb_t *t = &tns->thumbs[n]; + unsigned long col = win->bg.pixel; + int x = t->x + t->w, y = t->y + t->h; + + win_draw_rect(win, x - 1, y + 1, 1, tns->bw, true, 1, col); + win_draw_rect(win, x + 1, y - 1, tns->bw, 1, true, 1, col); + + if (mark) + col = win->fg.pixel; + + win_draw_rect(win, x, y, tns->bw + 2, tns->bw + 2, true, 1, col); + + if (!mark && n == *tns->sel) + tns_highlight(tns, n, true); + } +} + +void tns_highlight(tns_t *tns, int n, bool hl) +{ + if (n >= 0 && n < *tns->cnt && tns->thumbs[n].im != NULL) { + win_t *win = tns->win; + thumb_t *t = &tns->thumbs[n]; + unsigned long col = hl ? win->fg.pixel : win->bg.pixel; + int oxy = (tns->bw + 1) / 2 + 1, owh = tns->bw + 2; + + win_draw_rect(win, t->x - oxy, t->y - oxy, t->w + owh, t->h + owh, + false, tns->bw, col); + + if (tns->files[n].flags & FF_MARK) + tns_mark(tns, n, true); + } +} + +bool tns_move_selection(tns_t *tns, direction_t dir, int cnt) +{ + int old, max; + + old = *tns->sel; + cnt = cnt > 1 ? cnt : 1; + + switch (dir) { + case DIR_UP: + *tns->sel = MAX(*tns->sel - cnt * tns->cols, *tns->sel % tns->cols); + break; + case DIR_DOWN: + max = tns->cols * ((*tns->cnt - 1) / tns->cols) + + MIN((*tns->cnt - 1) % tns->cols, *tns->sel % tns->cols); + *tns->sel = MIN(*tns->sel + cnt * tns->cols, max); + break; + case DIR_LEFT: + *tns->sel = MAX(*tns->sel - cnt, 0); + break; + case DIR_RIGHT: + *tns->sel = MIN(*tns->sel + cnt, *tns->cnt - 1); + break; + } + + if (*tns->sel != old) { + tns_highlight(tns, old, false); + tns_check_view(tns, false); + if (!tns->dirty) + tns_highlight(tns, *tns->sel, true); + } + return *tns->sel != old; +} + +bool tns_scroll(tns_t *tns, direction_t dir, bool screen) +{ + int d, max, old; + + old = tns->first; + d = tns->cols * (screen ? tns->rows : 1); + + if (dir == DIR_DOWN) { + max = *tns->cnt - tns->cols * tns->rows; + if (*tns->cnt % tns->cols != 0) + max += tns->cols - *tns->cnt % tns->cols; + tns->first = MIN(tns->first + d, max); + } else if (dir == DIR_UP) { + tns->first = MAX(tns->first - d, 0); + } + + if (tns->first != old) { + tns_check_view(tns, true); + tns->dirty = true; + } + return tns->first != old; +} + +bool tns_zoom(tns_t *tns, int d) +{ + int i, oldzl; + + oldzl = tns->zl; + tns->zl += -(d < 0) + (d > 0); + tns->zl = MAX(tns->zl, 0); + tns->zl = MIN(tns->zl, ARRLEN(thumb_sizes)-1); + + tns->bw = ((thumb_sizes[tns->zl] - 1) >> 5) + 1; + tns->bw = MIN(tns->bw, 4); + tns->dim = thumb_sizes[tns->zl] + 2 * tns->bw + 6; + + if (tns->zl != oldzl) { + for (i = 0; i < *tns->cnt; i++) + tns_unload(tns, i); + tns->dirty = true; + } + return tns->zl != oldzl; +} + +int tns_translate(tns_t *tns, int x, int y) +{ + int n; + + if (x < tns->x || y < tns->y) + return -1; + + n = tns->first + (y - tns->y) / tns->dim * tns->cols + + (x - tns->x) / tns->dim; + if (n >= *tns->cnt) + n = -1; + + return n; +} diff --git a/suckless/sxiv/utf8.h b/suckless/sxiv/utf8.h new file mode 100644 index 00000000..8c6a7a0d --- /dev/null +++ b/suckless/sxiv/utf8.h @@ -0,0 +1,68 @@ +/* Branchless UTF-8 decoder + * + * This is free and unencumbered software released into the public domain. + */ +#ifndef UTF8_H +#define UTF8_H + +#include + +/* Decode the next character, C, from BUF, reporting errors in E. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in E, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +static void * +utf8_decode(void *buf, uint32_t *c, int *e) +{ + static const char lengths[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 + }; + static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + static const int shiftc[] = {0, 18, 12, 6, 0}; + static const int shifte[] = {0, 6, 4, 2, 0}; + + unsigned char *s = buf; + int len = lengths[s[0] >> 3]; + + /* Compute the pointer to the next character early so that the next + * iteration can start working on the next character. Neither Clang + * nor GCC figure out this reordering on their own. + */ + unsigned char *next = s + len + !len; + + /* Assume a four-byte character and load four bytes. Unused bits are + * shifted out. + */ + *c = (uint32_t)(s[0] & masks[len]) << 18; + *c |= (uint32_t)(s[1] & 0x3f) << 12; + *c |= (uint32_t)(s[2] & 0x3f) << 6; + *c |= (uint32_t)(s[3] & 0x3f) << 0; + *c >>= shiftc[len]; + + /* Accumulate the various error conditions. */ + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (s[1] & 0xc0) >> 2; + *e |= (s[2] & 0xc0) >> 4; + *e |= (s[3] ) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +#endif diff --git a/suckless/sxiv/util.c b/suckless/sxiv/util.c new file mode 100644 index 00000000..b956fd71 --- /dev/null +++ b/suckless/sxiv/util.c @@ -0,0 +1,213 @@ +/* Copyright 2011 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" + +#include +#include +#include +#include +#include +#include + +const char *progname; + +void* emalloc(size_t size) +{ + void *ptr; + + ptr = malloc(size); + if (ptr == NULL) + error(EXIT_FAILURE, errno, NULL); + return ptr; +} + +void* erealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL) + error(EXIT_FAILURE, errno, NULL); + return ptr; +} + +char* estrdup(const char *s) +{ + char *d; + size_t n = strlen(s) + 1; + + d = malloc(n); + if (d == NULL) + error(EXIT_FAILURE, errno, NULL); + memcpy(d, s, n); + return d; +} + +void error(int eval, int err, const char* fmt, ...) +{ + va_list ap; + + if (eval == 0 && options->quiet) + return; + + fflush(stdout); + fprintf(stderr, "%s: ", progname); + va_start(ap, fmt); + if (fmt != NULL) + vfprintf(stderr, fmt, ap); + va_end(ap); + if (err != 0) + fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err)); + fputc('\n', stderr); + + if (eval != 0) + exit(eval); +} + +void size_readable(float *size, const char **unit) +{ + const char *units[] = { "", "K", "M", "G" }; + int i; + + for (i = 0; i < ARRLEN(units) && *size > 1024.0; i++) + *size /= 1024.0; + *unit = units[MIN(i, ARRLEN(units) - 1)]; +} + +int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive) +{ + if (*dirname == '\0') + return -1; + + if ((rdir->dir = opendir(dirname)) == NULL) { + rdir->name = NULL; + rdir->stack = NULL; + return -1; + } + + rdir->stcap = 512; + rdir->stack = (char**) emalloc(rdir->stcap * sizeof(char*)); + rdir->stlen = 0; + + rdir->name = (char*) dirname; + rdir->d = 0; + rdir->recursive = recursive; + + return 0; +} + +int r_closedir(r_dir_t *rdir) +{ + int ret = 0; + + if (rdir->stack != NULL) { + while (rdir->stlen > 0) + free(rdir->stack[--rdir->stlen]); + free(rdir->stack); + rdir->stack = NULL; + } + + if (rdir->dir != NULL) { + if ((ret = closedir(rdir->dir)) == 0) + rdir->dir = NULL; + } + + if (rdir->d != 0) { + free(rdir->name); + rdir->name = NULL; + } + + return ret; +} + +char* r_readdir(r_dir_t *rdir, bool skip_dotfiles) +{ + size_t len; + char *filename; + struct dirent *dentry; + struct stat fstats; + + while (true) { + if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) { + if (dentry->d_name[0] == '.') { + if (skip_dotfiles) + continue; + if (dentry->d_name[1] == '\0') + continue; + if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0') + continue; + } + + len = strlen(rdir->name) + strlen(dentry->d_name) + 2; + filename = (char*) emalloc(len); + snprintf(filename, len, "%s%s%s", rdir->name, + rdir->name[strlen(rdir->name)-1] == '/' ? "" : "/", + dentry->d_name); + + if (stat(filename, &fstats) < 0) + continue; + if (S_ISDIR(fstats.st_mode)) { + /* put subdirectory on the stack */ + if (rdir->stlen == rdir->stcap) { + rdir->stcap *= 2; + rdir->stack = (char**) erealloc(rdir->stack, + rdir->stcap * sizeof(char*)); + } + rdir->stack[rdir->stlen++] = filename; + continue; + } + return filename; + } + + if (rdir->recursive && rdir->stlen > 0) { + /* open next subdirectory */ + closedir(rdir->dir); + if (rdir->d != 0) + free(rdir->name); + rdir->name = rdir->stack[--rdir->stlen]; + rdir->d = 1; + if ((rdir->dir = opendir(rdir->name)) == NULL) + error(0, errno, "%s", rdir->name); + continue; + } + /* no more entries */ + break; + } + return NULL; +} + +int r_mkdir(char *path) +{ + char c, *s = path; + struct stat st; + + while (*s != '\0') { + if (*s == '/') { + s++; + continue; + } + for (; *s != '\0' && *s != '/'; s++); + c = *s; + *s = '\0'; + if (mkdir(path, 0755) == -1) + if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) + return -1; + *s = c; + } + return 0; +} + diff --git a/suckless/sxiv/version.h b/suckless/sxiv/version.h new file mode 100644 index 00000000..8e8ed53f --- /dev/null +++ b/suckless/sxiv/version.h @@ -0,0 +1 @@ +#define VERSION "26" diff --git a/suckless/sxiv/window.c b/suckless/sxiv/window.c new file mode 100644 index 00000000..6f9a3906 --- /dev/null +++ b/suckless/sxiv/window.c @@ -0,0 +1,476 @@ +/* Copyright 2011-2013 Bert Muennich + * + * This file is part of sxiv. + * + * sxiv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * sxiv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with sxiv. If not, see . + */ + +#include "sxiv.h" +#define _WINDOW_CONFIG +#include "config.h" +#include "icon/data.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include + +#define RES_CLASS "Sxiv" + +enum { + H_TEXT_PAD = 5, + V_TEXT_PAD = 1 +}; + +static struct { + int name; + Cursor icon; +} cursors[CURSOR_COUNT] = { + { XC_left_ptr }, { XC_dotbox }, { XC_watch }, + { XC_sb_left_arrow }, { XC_sb_right_arrow } +}; + +static GC gc; + +static XftFont *font; +static int fontheight; +static double fontsize; +static int barheight; + +Atom atoms[ATOM_COUNT]; + +void win_init_font(const win_env_t *e, const char *fontstr) +{ + if ((font = XftFontOpenName(e->dpy, e->scr, fontstr)) == NULL) + error(EXIT_FAILURE, 0, "Error loading font '%s'", fontstr); + fontheight = font->ascent + font->descent; + FcPatternGetDouble(font->pattern, FC_SIZE, 0, &fontsize); + barheight = fontheight + 2 * V_TEXT_PAD; +} + +void win_alloc_color(const win_env_t *e, const char *name, XftColor *col) +{ + if (!XftColorAllocName(e->dpy, DefaultVisual(e->dpy, e->scr), + DefaultColormap(e->dpy, e->scr), name, col)) + { + error(EXIT_FAILURE, 0, "Error allocating color '%s'", name); + } +} + +const char* win_res(XrmDatabase db, const char *name, const char *def) +{ + char *type; + XrmValue ret; + + if (db != None && + XrmGetResource(db, name, name, &type, &ret) && + STREQ(type, "String")) + { + return ret.addr; + } else { + return def; + } +} + +#define INIT_ATOM_(atom) \ + atoms[ATOM_##atom] = XInternAtom(e->dpy, #atom, False); + +void win_init(win_t *win) +{ + win_env_t *e; + const char *bg, *fg, *f; + char *res_man; + XrmDatabase db; + + memset(win, 0, sizeof(win_t)); + + e = &win->env; + if ((e->dpy = XOpenDisplay(NULL)) == NULL) + error(EXIT_FAILURE, 0, "Error opening X display"); + + e->scr = DefaultScreen(e->dpy); + e->scrw = DisplayWidth(e->dpy, e->scr); + e->scrh = DisplayHeight(e->dpy, e->scr); + e->vis = DefaultVisual(e->dpy, e->scr); + e->cmap = DefaultColormap(e->dpy, e->scr); + e->depth = DefaultDepth(e->dpy, e->scr); + + if (setlocale(LC_CTYPE, "") == NULL || XSupportsLocale() == 0) + error(0, 0, "No locale support"); + + XrmInitialize(); + res_man = XResourceManagerString(e->dpy); + db = res_man != NULL ? XrmGetStringDatabase(res_man) : None; + + f = win_res(db, RES_CLASS ".font", "monospace-8"); + win_init_font(e, f); + + bg = win_res(db, RES_CLASS ".background", "white"); + fg = win_res(db, RES_CLASS ".foreground", "black"); + win_alloc_color(e, bg, &win->bg); + win_alloc_color(e, fg, &win->fg); + + win->bar.l.size = BAR_L_LEN; + win->bar.r.size = BAR_R_LEN; + /* 3 padding bytes needed by utf8_decode */ + win->bar.l.buf = emalloc(win->bar.l.size + 3); + win->bar.l.buf[0] = '\0'; + win->bar.r.buf = emalloc(win->bar.r.size + 3); + win->bar.r.buf[0] = '\0'; + win->bar.h = options->hide_bar ? 0 : barheight; + + INIT_ATOM_(WM_DELETE_WINDOW); + INIT_ATOM_(_NET_WM_NAME); + INIT_ATOM_(_NET_WM_ICON_NAME); + INIT_ATOM_(_NET_WM_ICON); + INIT_ATOM_(_NET_WM_STATE); + INIT_ATOM_(_NET_WM_STATE_FULLSCREEN); +} + +void win_open(win_t *win) +{ + int c, i, j, n; + long parent; + win_env_t *e; + XClassHint classhint; + unsigned long *icon_data; + XColor col; + Cursor *cnone = &cursors[CURSOR_NONE].icon; + char none_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + Pixmap none; + int gmask; + XSizeHints sizehints; + + e = &win->env; + parent = options->embed != 0 ? options->embed : RootWindow(e->dpy, e->scr); + + sizehints.flags = PWinGravity; + sizehints.win_gravity = NorthWestGravity; + + /* determine window offsets, width & height */ + if (options->geometry == NULL) + gmask = 0; + else + gmask = XParseGeometry(options->geometry, &win->x, &win->y, + &win->w, &win->h); + if ((gmask & WidthValue) != 0) + sizehints.flags |= USSize; + else + win->w = WIN_WIDTH; + if ((gmask & HeightValue) != 0) + sizehints.flags |= USSize; + else + win->h = WIN_HEIGHT; + if ((gmask & XValue) != 0) { + if ((gmask & XNegative) != 0) { + win->x += e->scrw - win->w; + sizehints.win_gravity = NorthEastGravity; + } + sizehints.flags |= USPosition; + } else { + win->x = 0; + } + if ((gmask & YValue) != 0) { + if ((gmask & YNegative) != 0) { + win->y += e->scrh - win->h; + sizehints.win_gravity = sizehints.win_gravity == NorthEastGravity + ? SouthEastGravity : SouthWestGravity; + } + sizehints.flags |= USPosition; + } else { + win->y = 0; + } + + win->xwin = XCreateWindow(e->dpy, parent, + win->x, win->y, win->w, win->h, 0, + e->depth, InputOutput, e->vis, 0, NULL); + if (win->xwin == None) + error(EXIT_FAILURE, 0, "Error creating X window"); + + XSelectInput(e->dpy, win->xwin, + ButtonReleaseMask | ButtonPressMask | KeyPressMask | + PointerMotionMask | StructureNotifyMask); + + for (i = 0; i < ARRLEN(cursors); i++) { + if (i != CURSOR_NONE) + cursors[i].icon = XCreateFontCursor(e->dpy, cursors[i].name); + } + if (XAllocNamedColor(e->dpy, DefaultColormap(e->dpy, e->scr), "black", + &col, &col) == 0) + { + error(EXIT_FAILURE, 0, "Error allocating color 'black'"); + } + none = XCreateBitmapFromData(e->dpy, win->xwin, none_data, 8, 8); + *cnone = XCreatePixmapCursor(e->dpy, none, none, &col, &col, 0, 0); + + gc = XCreateGC(e->dpy, win->xwin, 0, None); + + n = icons[ARRLEN(icons)-1].size; + icon_data = emalloc((n * n + 2) * sizeof(*icon_data)); + + for (i = 0; i < ARRLEN(icons); i++) { + n = 0; + icon_data[n++] = icons[i].size; + icon_data[n++] = icons[i].size; + + for (j = 0; j < icons[i].cnt; j++) { + for (c = icons[i].data[j] >> 4; c >= 0; c--) + icon_data[n++] = icon_colors[icons[i].data[j] & 0x0F]; + } + XChangeProperty(e->dpy, win->xwin, + atoms[ATOM__NET_WM_ICON], XA_CARDINAL, 32, + i == 0 ? PropModeReplace : PropModeAppend, + (unsigned char *) icon_data, n); + } + free(icon_data); + + win_set_title(win, "sxiv"); + + classhint.res_class = RES_CLASS; + classhint.res_name = options->res_name != NULL ? options->res_name : "sxiv"; + XSetClassHint(e->dpy, win->xwin, &classhint); + + XSetWMProtocols(e->dpy, win->xwin, &atoms[ATOM_WM_DELETE_WINDOW], 1); + + sizehints.width = win->w; + sizehints.height = win->h; + sizehints.x = win->x; + sizehints.y = win->y; + XSetWMNormalHints(win->env.dpy, win->xwin, &sizehints); + + win->h -= win->bar.h; + + win->buf.w = e->scrw; + win->buf.h = e->scrh; + win->buf.pm = XCreatePixmap(e->dpy, win->xwin, + win->buf.w, win->buf.h, e->depth); + XSetForeground(e->dpy, gc, win->bg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); + XSetWindowBackgroundPixmap(e->dpy, win->xwin, win->buf.pm); + + XMapWindow(e->dpy, win->xwin); + XFlush(e->dpy); + + if (options->fullscreen) + win_toggle_fullscreen(win); +} + +CLEANUP void win_close(win_t *win) +{ + int i; + + for (i = 0; i < ARRLEN(cursors); i++) + XFreeCursor(win->env.dpy, cursors[i].icon); + + XFreeGC(win->env.dpy, gc); + + XDestroyWindow(win->env.dpy, win->xwin); + XCloseDisplay(win->env.dpy); +} + +bool win_configure(win_t *win, XConfigureEvent *c) +{ + bool changed; + + changed = win->w != c->width || win->h + win->bar.h != c->height; + + win->x = c->x; + win->y = c->y; + win->w = c->width; + win->h = c->height - win->bar.h; + win->bw = c->border_width; + + return changed; +} + +void win_toggle_fullscreen(win_t *win) +{ + XEvent ev; + XClientMessageEvent *cm; + + memset(&ev, 0, sizeof(ev)); + ev.type = ClientMessage; + + cm = &ev.xclient; + cm->window = win->xwin; + cm->message_type = atoms[ATOM__NET_WM_STATE]; + cm->format = 32; + cm->data.l[0] = 2; // toggle + cm->data.l[1] = atoms[ATOM__NET_WM_STATE_FULLSCREEN]; + + XSendEvent(win->env.dpy, DefaultRootWindow(win->env.dpy), False, + SubstructureNotifyMask | SubstructureRedirectMask, &ev); +} + +void win_toggle_bar(win_t *win) +{ + if (win->bar.h != 0) { + win->h += win->bar.h; + win->bar.h = 0; + } else { + win->bar.h = barheight; + win->h -= win->bar.h; + } +} + +void win_clear(win_t *win) +{ + win_env_t *e; + + e = &win->env; + + if (win->w > win->buf.w || win->h + win->bar.h > win->buf.h) { + XFreePixmap(e->dpy, win->buf.pm); + win->buf.w = MAX(win->buf.w, win->w); + win->buf.h = MAX(win->buf.h, win->h + win->bar.h); + win->buf.pm = XCreatePixmap(e->dpy, win->xwin, + win->buf.w, win->buf.h, e->depth); + } + XSetForeground(e->dpy, gc, win->bg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, 0, win->buf.w, win->buf.h); +} + +#define TEXTWIDTH(win, text, len) \ + win_draw_text(win, NULL, NULL, 0, 0, text, len, 0) + +int win_draw_text(win_t *win, XftDraw *d, const XftColor *color, int x, int y, + char *text, int len, int w) +{ + int err, tw = 0; + char *t, *next; + uint32_t rune; + XftFont *f; + FcCharSet *fccharset; + XGlyphInfo ext; + + for (t = text; t - text < len; t = next) { + next = utf8_decode(t, &rune, &err); + if (XftCharExists(win->env.dpy, font, rune)) { + f = font; + } else { /* fallback font */ + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, rune); + f = XftFontOpen(win->env.dpy, win->env.scr, FC_CHARSET, FcTypeCharSet, + fccharset, FC_SCALABLE, FcTypeBool, FcTrue, + FC_SIZE, FcTypeDouble, fontsize, NULL); + FcCharSetDestroy(fccharset); + } + XftTextExtentsUtf8(win->env.dpy, f, (XftChar8*)t, next - t, &ext); + tw += ext.xOff; + if (tw <= w) { + XftDrawStringUtf8(d, color, f, x, y, (XftChar8*)t, next - t); + x += ext.xOff; + } + if (f != font) + XftFontClose(win->env.dpy, f); + } + return tw; +} + +void win_draw_bar(win_t *win) +{ + int len, x, y, w, tw; + win_env_t *e; + win_bar_t *l, *r; + XftDraw *d; + + if ((l = &win->bar.l)->buf == NULL || (r = &win->bar.r)->buf == NULL) + return; + + e = &win->env; + y = win->h + font->ascent + V_TEXT_PAD; + w = win->w - 2*H_TEXT_PAD; + d = XftDrawCreate(e->dpy, win->buf.pm, DefaultVisual(e->dpy, e->scr), + DefaultColormap(e->dpy, e->scr)); + + XSetForeground(e->dpy, gc, win->fg.pixel); + XFillRectangle(e->dpy, win->buf.pm, gc, 0, win->h, win->w, win->bar.h); + + XSetForeground(e->dpy, gc, win->bg.pixel); + XSetBackground(e->dpy, gc, win->fg.pixel); + + if ((len = strlen(r->buf)) > 0) { + if ((tw = TEXTWIDTH(win, r->buf, len)) > w) + return; + x = win->w - tw - H_TEXT_PAD; + w -= tw; + win_draw_text(win, d, &win->bg, x, y, r->buf, len, tw); + } + if ((len = strlen(l->buf)) > 0) { + x = H_TEXT_PAD; + w -= 2 * H_TEXT_PAD; /* gap between left and right parts */ + win_draw_text(win, d, &win->bg, x, y, l->buf, len, w); + } + XftDrawDestroy(d); +} + +void win_draw(win_t *win) +{ + if (win->bar.h > 0) + win_draw_bar(win); + + XSetWindowBackgroundPixmap(win->env.dpy, win->xwin, win->buf.pm); + XClearWindow(win->env.dpy, win->xwin); + XFlush(win->env.dpy); +} + +void win_draw_rect(win_t *win, int x, int y, int w, int h, bool fill, int lw, + unsigned long col) +{ + XGCValues gcval; + + gcval.line_width = lw; + gcval.foreground = col; + XChangeGC(win->env.dpy, gc, GCForeground | GCLineWidth, &gcval); + + if (fill) + XFillRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); + else + XDrawRectangle(win->env.dpy, win->buf.pm, gc, x, y, w, h); +} + +void win_set_title(win_t *win, const char *title) +{ + XStoreName(win->env.dpy, win->xwin, title); + XSetIconName(win->env.dpy, win->xwin, title); + + XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_NAME], + XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, + PropModeReplace, (unsigned char *) title, strlen(title)); + XChangeProperty(win->env.dpy, win->xwin, atoms[ATOM__NET_WM_ICON_NAME], + XInternAtom(win->env.dpy, "UTF8_STRING", False), 8, + PropModeReplace, (unsigned char *) title, strlen(title)); +} + +void win_set_cursor(win_t *win, cursor_t cursor) +{ + if (cursor >= 0 && cursor < ARRLEN(cursors)) { + XDefineCursor(win->env.dpy, win->xwin, cursors[cursor].icon); + XFlush(win->env.dpy); + } +} + +void win_cursor_pos(win_t *win, int *x, int *y) +{ + int i; + unsigned int ui; + Window w; + + if (!XQueryPointer(win->env.dpy, win->xwin, &w, &w, &i, &i, x, y, &ui)) + *x = *y = 0; +} + diff --git a/suckless/xbanish/.gitignore b/suckless/xbanish/.gitignore new file mode 100644 index 00000000..e3c353ed --- /dev/null +++ b/suckless/xbanish/.gitignore @@ -0,0 +1,57 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +slock + +*.orig +*.rej diff --git a/suckless/xbanish/LICENSE b/suckless/xbanish/LICENSE new file mode 100644 index 00000000..884c6fca --- /dev/null +++ b/suckless/xbanish/LICENSE @@ -0,0 +1,14 @@ +xbanish +Copyright (c) 2013-2021 joshua stein + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/suckless/xbanish/Makefile b/suckless/xbanish/Makefile new file mode 100644 index 00000000..fcd0f4f9 --- /dev/null +++ b/suckless/xbanish/Makefile @@ -0,0 +1,39 @@ +# vim:ts=8 + +CC ?= cc +CFLAGS ?= -O2 +CFLAGS += -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes -Wunused + +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/man/man1 + +INSTALL_PROGRAM ?= install -s +INSTALL_DATA ?= install + +X11BASE ?= /usr/X11R6 +INCLUDES?= -I$(X11BASE)/include +LDPATH ?= -L$(X11BASE)/lib +LIBS += -lX11 -lXfixes -lXi + +PROG = xbanish +OBJS = xbanish.o + +all: $(PROG) + +$(PROG): $(OBJS) + $(CC) $(OBJS) $(LDPATH) $(LIBS) -o $@ + +$(OBJS): *.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + $(INSTALL_PROGRAM) $(PROG) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(MANDIR) + $(INSTALL_DATA) -m 644 xbanish.1 $(DESTDIR)$(MANDIR)/xbanish.1 + +clean: + rm -f $(PROG) $(OBJS) + +.PHONY: all install clean diff --git a/suckless/xbanish/xbanish b/suckless/xbanish/xbanish new file mode 100755 index 0000000000000000000000000000000000000000..8733f7222076293fde76f96eacfa467a2c142a24 GIT binary patch literal 27016 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 literal 0 HcmV?d00001 diff --git a/suckless/xbanish/xbanish.1 b/suckless/xbanish/xbanish.1 new file mode 100644 index 00000000..894cd5f2 --- /dev/null +++ b/suckless/xbanish/xbanish.1 @@ -0,0 +1,60 @@ +.Dd $Mdocdate: September 16 2019$ +.Dt XBANISH 1 +.Os +.Sh NAME +.Nm xbanish +.Nd hide the X11 mouse cursor when a key is pressed +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl d +.Op Fl i Ar modifier +.Op Fl m Ar nw|ne|sw|se +.Sh DESCRIPTION +.Nm +hides the X11 mouse cursor when a key is pressed. +The cursor is shown again when it is moved or a mouse button is pressed. +This is similar to the +.Xr xterm 1 +setting +.Ic pointerMode +but the effect is global in the X11 session. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl a +Always keep mouse cursor hidden while +.Nm +is running. +.It Fl d +Print debugging messages to stdout. +.It Fl i Ar modifier +Ignore pressed key if +.Ar modifier +is used. +Modifiers are: +.Ic shift , +.Ic lock +(CapsLock), +.Ic control , +.Ic mod1 +(Alt or Meta), +.Ic mod2 +(NumLock), +.Ic mod3 +(Hyper), +.Ic mod4 +(Super, Windows, or Command), +.Ic mod5 +(ISO Level 3 Shift), and +.Ic all +(All of the above). +.It Fl m Ar nw|ne|sw|se +When hiding the mouse cursor, move it to this corner of the screen, +then move it back when showing the cursor. +.El +.Sh SEE ALSO +.Xr XFixes 3 +.Sh AUTHORS +.Nm +was written by +.An joshua stein Aq Mt jcs@jcs.org . diff --git a/suckless/xbanish/xbanish.c b/suckless/xbanish/xbanish.c new file mode 100644 index 00000000..d64e61ba --- /dev/null +++ b/suckless/xbanish/xbanish.c @@ -0,0 +1,487 @@ +/* + * xbanish + * Copyright (c) 2013-2021 joshua stein + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void hide_cursor(void); +void show_cursor(void); +void snoop_root(void); +int snoop_xinput(Window); +void snoop_legacy(Window); +void usage(char *); +int swallow_error(Display *, XErrorEvent *); + +/* xinput event type ids to be filled in later */ +static int button_press_type = -1; +static int button_release_type = -1; +static int key_press_type = -1; +static int key_release_type = -1; +static int motion_type = -1; +static int device_change_type = -1; +static long last_device_change = -1; + +static Display *dpy; +static int hiding = 0, legacy = 0, always_hide = 0; +static unsigned char ignored; + +static int debug = 0; +#define DPRINTF(x) { if (debug) { printf x; } }; + +static int move = 0, move_x, move_y; +enum move_types { + MOVE_NW = 1, + MOVE_NE, + MOVE_SW, + MOVE_SE, +}; + +int +main(int argc, char *argv[]) +{ + int ch, i; + XEvent e; + XGenericEventCookie *cookie; + + struct mod_lookup { + char *name; + int mask; + } mods[] = { + {"shift", ShiftMask}, {"lock", LockMask}, + {"control", ControlMask}, {"mod1", Mod1Mask}, + {"mod2", Mod2Mask}, {"mod3", Mod3Mask}, + {"mod4", Mod4Mask}, {"mod5", Mod5Mask}, + {"all", -1}, + }; + + while ((ch = getopt(argc, argv, "adi:m:")) != -1) + switch (ch) { + case 'a': + always_hide = 1; + break; + case 'd': + debug = 1; + break; + case 'i': + for (i = 0; + i < sizeof(mods) / sizeof(struct mod_lookup); i++) + if (strcasecmp(optarg, mods[i].name) == 0) + ignored |= mods[i].mask; + + break; + case 'm': + if (strcmp(optarg, "nw") == 0) + move = MOVE_NW; + else if (strcmp(optarg, "ne") == 0) + move = MOVE_NE; + else if (strcmp(optarg, "sw") == 0) + move = MOVE_SW; + else if (strcmp(optarg, "se") == 0) + move = MOVE_SE; + else { + warnx("invalid '-m' argument"); + usage(argv[0]); + } + break; + default: + usage(argv[0]); + } + + argc -= optind; + argv += optind; + + if (!(dpy = XOpenDisplay(NULL))) + errx(1, "can't open display %s", XDisplayName(NULL)); + +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + XSetErrorHandler(swallow_error); + + snoop_root(); + + if (always_hide) + hide_cursor(); + + for (;;) { + cookie = &e.xcookie; + XNextEvent(dpy, &e); + + int etype = e.type; + if (e.type == motion_type) + etype = MotionNotify; + else if (e.type == key_press_type || + e.type == key_release_type) + etype = KeyRelease; + else if (e.type == button_press_type || + e.type == button_release_type) + etype = ButtonRelease; + else if (e.type == device_change_type) { + XDevicePresenceNotifyEvent *xdpe = + (XDevicePresenceNotifyEvent *)&e; + if (last_device_change == xdpe->serial) + continue; + snoop_root(); + last_device_change = xdpe->serial; + continue; + } + + switch (etype) { + case KeyRelease: + if (ignored) { + unsigned int state = 0; + + /* masks are only set on key release, if + * ignore is set we must throw out non-release + * events here */ + if (e.type == key_press_type) { + break; + } + + /* extract modifier state */ + if (e.type == key_release_type) { + /* xinput device event */ + XDeviceKeyEvent *key = + (XDeviceKeyEvent *) &e; + state = key->state; + } else if (e.type == KeyRelease) { + /* legacy event */ + state = e.xkey.state; + } + + if (state & ignored) { + DPRINTF(("ignoring key %d\n", state)); + break; + } + } + + hide_cursor(); + break; + + case ButtonRelease: + case MotionNotify: + if (!always_hide) + show_cursor(); + break; + + case CreateNotify: + if (legacy) { + DPRINTF(("new window, snooping on it\n")); + + /* not sure why snooping directly on the window + * doesn't work, so snoop on all windows from + * its parent (probably root) */ + snoop_legacy(e.xcreatewindow.parent); + } + break; + + case GenericEvent: + /* xi2 raw event */ + XGetEventData(dpy, cookie); + XIDeviceEvent *xie = (XIDeviceEvent *)cookie->data; + + switch (xie->evtype) { + case XI_RawMotion: + case XI_RawButtonPress: + if (!always_hide) + show_cursor(); + break; + + case XI_RawButtonRelease: + break; + + default: + DPRINTF(("unknown XI event type %d\n", + xie->evtype)); + } + + XFreeEventData(dpy, cookie); + break; + + default: + DPRINTF(("unknown event type %d\n", e.type)); + } + } +} + +void +hide_cursor(void) +{ + Window win; + int x, y, h, w, junk; + unsigned int ujunk; + + DPRINTF(("keystroke, %shiding cursor\n", (hiding ? "already " : ""))); + + if (hiding) + return; + + if (move) { + if (XQueryPointer(dpy, DefaultRootWindow(dpy), + &win, &win, &x, &y, &junk, &junk, &ujunk)) { + move_x = x; + move_y = y; + + h = XHeightOfScreen(DefaultScreenOfDisplay(dpy)); + w = XWidthOfScreen(DefaultScreenOfDisplay(dpy)); + + switch (move) { + case MOVE_NW: + x = 0; + y = 0; + break; + case MOVE_NE: + x = w; + y = 0; + break; + case MOVE_SW: + x = 0; + y = h; + break; + case MOVE_SE: + x = w; + y = h; + break; + } + + XWarpPointer(dpy, None, DefaultRootWindow(dpy), + 0, 0, 0, 0, x, y); + } else { + move_x = -1; + move_y = -1; + warn("failed finding cursor coordinates"); + } + } + + XFixesHideCursor(dpy, DefaultRootWindow(dpy)); + hiding = 1; +} + +void +show_cursor(void) +{ + DPRINTF(("mouse moved, %sunhiding cursor\n", + (hiding ? "" : "already "))); + + if (!hiding) + return; + + if (move && move_x != -1 && move_y != -1) + XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0, + move_x, move_y); + + XFixesShowCursor(dpy, DefaultRootWindow(dpy)); + hiding = 0; +} + +void +snoop_root(void) +{ + if (snoop_xinput(DefaultRootWindow(dpy)) == 0) { + DPRINTF(("no XInput devices found, using legacy snooping")); + legacy = 1; + snoop_legacy(DefaultRootWindow(dpy)); + } +} + +int +snoop_xinput(Window win) +{ + int opcode, event, error, numdevs, i, j; + int major, minor, rc, rawmotion = 0; + int ev = 0; + unsigned char mask[(XI_LASTEVENT + 7)/8]; + XDeviceInfo *devinfo = NULL; + XInputClassInfo *ici; + XDevice *device; + XIEventMask evmasks[1]; + XEventClass class_presence; + + if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) { + DPRINTF(("XInput extension not available")); + return 0; + } + + /* + * If we support xinput 2, use that for raw motion and button events to + * get pointer data when the cursor is over a Chromium window. We + * could also use this to get raw key input and avoid the other XInput + * stuff, but we may need to be able to examine the key value later to + * filter out ignored keys. + */ + major = minor = 2; + rc = XIQueryVersion(dpy, &major, &minor); + if (rc != BadRequest) { + memset(mask, 0, sizeof(mask)); + + XISetMask(mask, XI_RawMotion); + XISetMask(mask, XI_RawButtonPress); + evmasks[0].deviceid = XIAllMasterDevices; + evmasks[0].mask_len = sizeof(mask); + evmasks[0].mask = mask; + + XISelectEvents(dpy, win, evmasks, 1); + XFlush(dpy); + + rawmotion = 1; + + DPRINTF(("using xinput2 raw motion events\n")); + } + + devinfo = XListInputDevices(dpy, &numdevs); + XEventClass event_list[numdevs * 2]; + for (i = 0; i < numdevs; i++) { + if (devinfo[i].use != IsXExtensionKeyboard && + devinfo[i].use != IsXExtensionPointer) + continue; + + if (!(device = XOpenDevice(dpy, devinfo[i].id))) + break; + + for (ici = device->classes, j = 0; j < devinfo[i].num_classes; + ici++, j++) { + switch (ici->input_class) { + case KeyClass: + DPRINTF(("attaching to keyboard device %s " + "(use %d)\n", devinfo[i].name, + devinfo[i].use)); + + DeviceKeyPress(device, key_press_type, + event_list[ev]); ev++; + DeviceKeyRelease(device, key_release_type, + event_list[ev]); ev++; + break; + + case ButtonClass: + if (rawmotion) + continue; + + DPRINTF(("attaching to buttoned device %s " + "(use %d)\n", devinfo[i].name, + devinfo[i].use)); + + DeviceButtonPress(device, button_press_type, + event_list[ev]); ev++; + DeviceButtonRelease(device, + button_release_type, event_list[ev]); ev++; + break; + + case ValuatorClass: + if (rawmotion) + continue; + + DPRINTF(("attaching to pointing device %s " + "(use %d)\n", devinfo[i].name, + devinfo[i].use)); + + DeviceMotionNotify(device, motion_type, + event_list[ev]); ev++; + break; + } + } + + XCloseDevice(dpy, device); + + if (XSelectExtensionEvent(dpy, win, event_list, ev)) { + warn("error selecting extension events"); + ev = 0; + goto done; + } + } + + DevicePresence(dpy, device_change_type, class_presence); + if (XSelectExtensionEvent(dpy, win, &class_presence, 1)) { + warn("error selecting extension events"); + ev = 0; + goto done; + } + +done: + if (devinfo != NULL) + XFreeDeviceList(devinfo); + + return ev; +} + +void +snoop_legacy(Window win) +{ + Window parent, root, *kids = NULL; + XSetWindowAttributes sattrs; + unsigned int nkids = 0, i; + + /* + * Firefox stops responding to keys when KeyPressMask is used, so + * settle for KeyReleaseMask + */ + int type = PointerMotionMask | KeyReleaseMask | Button1MotionMask | + Button2MotionMask | Button3MotionMask | Button4MotionMask | + Button5MotionMask | ButtonMotionMask; + + if (XQueryTree(dpy, win, &root, &parent, &kids, &nkids) == FALSE) { + warn("can't query window tree\n"); + goto done; + } + + XSelectInput(dpy, root, type); + + /* listen for newly mapped windows */ + sattrs.event_mask = SubstructureNotifyMask; + XChangeWindowAttributes(dpy, root, CWEventMask, &sattrs); + + for (i = 0; i < nkids; i++) { + XSelectInput(dpy, kids[i], type); + snoop_legacy(kids[i]); + } + +done: + if (kids != NULL) + XFree(kids); /* hide yo kids */ +} + +void +usage(char *progname) +{ + fprintf(stderr, "usage: %s [-a] [-d] [-i mod] [-m nw|ne|sw|se]\n", + progname); + exit(1); +} + +int +swallow_error(Display *d, XErrorEvent *e) +{ + if (e->error_code == BadWindow) + /* no biggie */ + return 0; + else if (e->error_code & FirstExtensionError) + /* error requesting input on a particular xinput device */ + return 0; + else + errx(1, "got X error %d", e->error_code); +}