@@ -2,8 +2,9 @@ | |||||
# Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler | # Example for $XDG_CONFIG_HOME/sxiv/exec/key-handler | ||||
# Called by sxiv(1) after the external prefix key (C-x by default) is pressed. | # 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, followed by the paths of | |||||
# all marked images or the path of the current image, if no image is marked. | |||||
# The next key combo is passed as its first argument. The paths of all marked | |||||
# images--or of the current image, if no image is marked--are passed via stdin, | |||||
# one file path per line. | |||||
# sxiv(1) blocks until this script terminates. It then checks which images | # sxiv(1) blocks until this script terminates. It then checks which images | ||||
# have been modified and reloads them. | # have been modified and reloads them. | ||||
@@ -11,12 +12,13 @@ | |||||
# where C/M/S indicate Ctrl/Meta(Alt)/Shift modifier states and KEY is the X | # 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. | # keysym as listed in /usr/include/X11/keysymdef.h without the "XK_" prefix. | ||||
readonly KEY="$1"; shift | |||||
readonly KEY="$1"; | |||||
readonly TAGFILE="$HOME/.config/sxiv/tags" | readonly TAGFILE="$HOME/.config/sxiv/tags" | ||||
readonly TMPFILE="/tmp/sxiv.$$" | |||||
rotate() { | rotate() { | ||||
degree="$1"; shift | |||||
for file in "$@"; do | |||||
degree="$1" | |||||
while read file; do | |||||
case "$(file -b -i "$file")" in | case "$(file -b -i "$file")" in | ||||
image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; | image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;; | ||||
*) mogrify -rotate "$degree" "$file" ;; | *) mogrify -rotate "$degree" "$file" ;; | ||||
@@ -28,25 +30,27 @@ tag_add() { | |||||
>>"$TAGFILE" | >>"$TAGFILE" | ||||
tags=$(dmenu <"$TAGFILE" | tr '\n' ',') | tags=$(dmenu <"$TAGFILE" | tr '\n' ',') | ||||
[ -z "$tags" ] && return | [ -z "$tags" ] && return | ||||
iptckwed -a "$tags" "$@" | |||||
iptckwed -i -a "$tags" | |||||
echo -n "$tags" | tr ',' '\n' | sort - "$TAGFILE" | uniq >"$TAGFILE.new" | echo -n "$tags" | tr ',' '\n' | sort - "$TAGFILE" | uniq >"$TAGFILE.new" | ||||
mv -f "$TAGFILE"{.new,} | mv -f "$TAGFILE"{.new,} | ||||
} | } | ||||
tag_del() { | tag_del() { | ||||
tags=$(iptckwed -ql "$@" | cut -f 2 | tr ',' '\n' | sort | uniq | dmenu | tr '\n' ',') | |||||
cat >"$TMPFILE" | |||||
tags=$(iptckwed -iql <"$TMPFILE" | cut -f 2 | tr ',' '\n' | sort | uniq | dmenu | tr '\n' ',') | |||||
[ -z "$tags" ] && return | [ -z "$tags" ] && return | ||||
iptckwed -r "$tags" "$@" | |||||
iptckwed -i -r "$tags" <"$TMPFILE" | |||||
rm -f "$TMPFILE" | |||||
} | } | ||||
case "$KEY" in | case "$KEY" in | ||||
"C-c") echo -n "$@" | xsel -i ;; | |||||
"C-e") for file in "$@"; do urxvt -bg "#444" -fg "#eee" -sl 0 -title "$file" -e sh -c "exiv2 pr -q -pa '$file' | less" & done ;; | |||||
"C-g") gimp "$@" & ;; | |||||
"C-comma") rotate 270 "$@" ;; | |||||
"C-period") rotate 90 "$@" ;; | |||||
"C-slash") rotate 180 "$@" ;; | |||||
"C-t") tag_add "$@" ;; | |||||
"M-T") tag_del "$@" ;; | |||||
"C-c") tr '\n' ' ' | xsel -i ;; | |||||
"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-comma") rotate 270 ;; | |||||
"C-period") rotate 90 ;; | |||||
"C-slash") rotate 180 ;; | |||||
"C-t") tag_add ;; | |||||
"M-T") tag_del ;; | |||||
esac | esac | ||||
@@ -481,12 +481,13 @@ void clear_resize(void) | |||||
void run_key_handler(const char *key, unsigned int mask) | void run_key_handler(const char *key, unsigned int mask) | ||||
{ | { | ||||
pid_t pid; | pid_t pid; | ||||
int i, j, retval, status; | |||||
int fcnt = mode == MODE_THUMB && markcnt > 0 ? markcnt : 1; | |||||
FILE *pfs; | |||||
bool marked = mode == MODE_THUMB && markcnt > 0; | |||||
bool changed = false; | bool changed = false; | ||||
char **args, kstr[32], oldbar[BAR_L_LEN]; | |||||
struct stat *oldst, newst; | |||||
struct { int fn; struct stat st; } *finfo; | |||||
int f, i, pfd[2], retval, status; | |||||
int fcnt = marked ? markcnt : 1; | |||||
char kstr[32], oldbar[BAR_L_LEN]; | |||||
struct stat *oldst, st; | |||||
if (keyhandler.cmd == NULL) { | if (keyhandler.cmd == NULL) { | ||||
if (!keyhandler.warned) { | if (!keyhandler.warned) { | ||||
@@ -498,55 +499,66 @@ void run_key_handler(const char *key, unsigned int mask) | |||||
if (key == NULL) | if (key == NULL) | ||||
return; | return; | ||||
finfo = s_malloc(fcnt * sizeof(*finfo)); | |||||
args = s_malloc((fcnt + 3) * sizeof(*args)); | |||||
args[0] = keyhandler.cmd; | |||||
args[1] = kstr; | |||||
args[fcnt+2] = NULL; | |||||
if (mode == MODE_IMAGE || markcnt == 0) { | |||||
finfo[0].fn = fileidx; | |||||
stat(files[fileidx].path, &finfo[0].st); | |||||
args[2] = (char*) files[fileidx].path; | |||||
} else for (i = j = 0; i < filecnt; i++) { | |||||
if (files[i].marked) { | |||||
finfo[j].fn = i; | |||||
stat(files[i].path, &finfo[j++].st); | |||||
args[j+1] = (char*) files[i].path; | |||||
} | |||||
if (pipe(pfd) < 0) { | |||||
warn("could not create pipe for key handler"); | |||||
return; | |||||
} | } | ||||
snprintf(kstr, sizeof(kstr), "%s%s%s%s", | |||||
mask & ControlMask ? "C-" : "", | |||||
mask & Mod1Mask ? "M-" : "", | |||||
mask & ShiftMask ? "S-" : "", key); | |||||
if ((pfs = fdopen(pfd[1], "w")) == NULL) { | |||||
close(pfd[0]), close(pfd[1]); | |||||
warn("could not open pipe for key handler"); | |||||
return; | |||||
} | |||||
oldst = s_malloc(fcnt * sizeof(*oldst)); | |||||
memcpy(oldbar, win.bar.l.buf, sizeof(oldbar)); | memcpy(oldbar, win.bar.l.buf, sizeof(oldbar)); | ||||
strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); | strncpy(win.bar.l.buf, "Running key handler...", win.bar.l.size); | ||||
win_draw(&win); | win_draw(&win); | ||||
win_set_cursor(&win, CURSOR_WATCH); | 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) { | if ((pid = fork()) == 0) { | ||||
execv(keyhandler.cmd, args); | |||||
close(pfd[1]); | |||||
dup2(pfd[0], 0); | |||||
execl(keyhandler.cmd, keyhandler.cmd, kstr, NULL); | |||||
warn("could not exec key handler"); | warn("could not exec key handler"); | ||||
exit(EXIT_FAILURE); | exit(EXIT_FAILURE); | ||||
} else if (pid < 0) { | |||||
} | |||||
close(pfd[0]); | |||||
if (pid < 0) { | |||||
fclose(pfs); | |||||
warn("could not fork key handler"); | warn("could not fork key handler"); | ||||
goto end; | goto end; | ||||
} | } | ||||
for (f = i = 0; f < fcnt; i++) { | |||||
if ((marked && files[i].marked) || (!marked && i == fileidx)) { | |||||
stat(files[i].path, &oldst[f]); | |||||
fprintf(pfs, "%s\n", files[i].name); | |||||
f++; | |||||
} | |||||
} | |||||
fclose(pfs); | |||||
waitpid(pid, &status, 0); | waitpid(pid, &status, 0); | ||||
retval = WEXITSTATUS(status); | retval = WEXITSTATUS(status); | ||||
if (WIFEXITED(status) == 0 || retval != 0) | if (WIFEXITED(status) == 0 || retval != 0) | ||||
warn("key handler exited with non-zero return value: %d", retval); | warn("key handler exited with non-zero return value: %d", retval); | ||||
for (i = 0; i < fcnt; i++) { | |||||
oldst = &finfo[i].st; | |||||
if (stat(files[finfo[i].fn].path, &newst) != 0 || | |||||
memcmp(&oldst->st_mtime, &newst.st_mtime, sizeof(newst.st_mtime)) != 0) | |||||
{ | |||||
if (tns.thumbs != NULL) { | |||||
tns_unload(&tns, finfo[i].fn); | |||||
tns.loadnext = MIN(tns.loadnext, finfo[i].fn); | |||||
for (f = i = 0; f < fcnt; i++) { | |||||
if ((marked && files[i].marked) || (!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; | |||||
} | } | ||||
changed = true; | |||||
f++; | |||||
} | } | ||||
} | } | ||||
end: | end: | ||||
@@ -558,10 +570,9 @@ end: | |||||
memcpy(win.bar.l.buf, oldbar, win.bar.l.size); | memcpy(win.bar.l.buf, oldbar, win.bar.l.size); | ||||
} | } | ||||
} | } | ||||
free(oldst); | |||||
reset_cursor(); | reset_cursor(); | ||||
redraw(); | redraw(); | ||||
free(finfo); | |||||
free(args); | |||||
} | } | ||||
#define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) | #define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask)) | ||||
@@ -358,8 +358,9 @@ located in | |||||
.IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . | .IR $XDG_CONFIG_HOME/sxiv/exec/key-handler . | ||||
The handler is invoked by pressing | The handler is invoked by pressing | ||||
.BR Ctrl-x . | .BR Ctrl-x . | ||||
The next key combo is then passed as its first argument, followed by the paths | |||||
of all marked images or the path of the current image, if no image is marked. | |||||
The next key combo is passed as its first argument. The paths of all marked | |||||
images--or of the current image, if no image is marked--are passed via stdin, | |||||
one file path per line. | |||||
sxiv(1) will block until the handler terminates. It then checks which images | sxiv(1) will block until the handler terminates. It then checks which images | ||||
have been modified and reloads them. | have been modified and reloads them. | ||||