diff --git a/exec/key-handler b/exec/key-handler
index 0586dde..4f83b10 100644
--- a/exec/key-handler
+++ b/exec/key-handler
@@ -2,8 +2,9 @@
 
 # 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, 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
 # 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
 # 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 TMPFILE="/tmp/sxiv.$$"
 
 rotate() {
-	degree="$1"; shift
-	for file in "$@"; do
+	degree="$1"
+	while read file; do
 		case "$(file -b -i "$file")" in
 		image/jpeg*) jpegtran -rotate "$degree" -copy all -outfile "$file" "$file" ;;
 		*)           mogrify  -rotate "$degree" "$file" ;;
@@ -28,25 +30,27 @@ tag_add() {
 	>>"$TAGFILE"
 	tags=$(dmenu <"$TAGFILE" | tr '\n' ',')
 	[ -z "$tags" ] && return
-	iptckwed -a "$tags" "$@"
+	iptckwed -i -a "$tags"
 	echo -n "$tags" | tr ',' '\n' | sort - "$TAGFILE" | uniq >"$TAGFILE.new"
 	mv -f "$TAGFILE"{.new,}
 }
 
 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
-	iptckwed -r "$tags" "$@"
+	iptckwed -i -r "$tags" <"$TMPFILE"
+	rm -f "$TMPFILE"
 }
 
 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
 
diff --git a/main.c b/main.c
index f8dbab8..57798c4 100644
--- a/main.c
+++ b/main.c
@@ -481,12 +481,13 @@ void clear_resize(void)
 void run_key_handler(const char *key, unsigned int mask)
 {
 	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;
-	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.warned) {
@@ -498,55 +499,66 @@ void run_key_handler(const char *key, unsigned int mask)
 	if (key == NULL)
 		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));
 	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) {
-		execv(keyhandler.cmd, args);
+		close(pfd[1]);
+		dup2(pfd[0], 0);
+		execl(keyhandler.cmd, keyhandler.cmd, kstr, NULL);
 		warn("could not exec key handler");
 		exit(EXIT_FAILURE);
-	} else if (pid < 0) {
+	}
+	close(pfd[0]);
+	if (pid < 0) {
+		fclose(pfs);
 		warn("could not fork key handler");
 		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);
 	retval = WEXITSTATUS(status);
 	if (WIFEXITED(status) == 0 || retval != 0)
 		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:
@@ -558,10 +570,9 @@ end:
 			memcpy(win.bar.l.buf, oldbar, win.bar.l.size);
 		}
 	}
+	free(oldst);
 	reset_cursor();
 	redraw();
-	free(finfo);
-	free(args);
 }
 
 #define MODMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask))
diff --git a/sxiv.1 b/sxiv.1
index 70c9007..e4eb11e 100644
--- a/sxiv.1
+++ b/sxiv.1
@@ -358,8 +358,9 @@ located in
 .IR $XDG_CONFIG_HOME/sxiv/exec/key-handler .
 The handler is invoked by pressing
 .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
 have been modified and reloads them.