From b8ff1677b1cbb4d3e769861d1e14234c6a38a80a Mon Sep 17 00:00:00 2001
From: Bert <ber.t@gmx.com>
Date: Tue, 26 Jul 2011 18:01:29 +0200
Subject: [PATCH] Major code refactoring

- Configurable key and mouse mappings in config.h
- Put event handling code from main.c into events.[ch]
---
 Makefile  |   4 +-
 config.h  | 124 +++++++++--
 events.c  | 562 ++++++++++++++++++++++++++++++++++++++++++++++
 events.h  |  67 ++++++
 image.c   |   6 +-
 main.c    | 650 ++++--------------------------------------------------
 options.c |   1 +
 sxiv.1    |   8 +-
 thumbs.c  |  10 +-
 types.h   |  14 +-
 util.c    |   8 +-
 window.c  |   2 +
 12 files changed, 808 insertions(+), 648 deletions(-)
 create mode 100644 events.c
 create mode 100644 events.h

diff --git a/Makefile b/Makefile
index 52622ca..2bdcce3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 all: sxiv
 
-VERSION = git-20110722
+VERSION = git-20110726
 
 CC = gcc
 DESTDIR =
@@ -9,7 +9,7 @@ CFLAGS = -Wall -pedantic -DVERSION=\"$(VERSION)\"
 LDFLAGS =
 LIBS = -lX11 -lImlib2
 
-SRC = image.c main.c options.c thumbs.c util.c window.c
+SRC = events.o image.c main.c options.c thumbs.c util.c window.c
 OBJ = $(SRC:.c=.o)
 
 sxiv:	$(OBJ)
diff --git a/config.h b/config.h
index d852091..aac4244 100644
--- a/config.h
+++ b/config.h
@@ -1,36 +1,120 @@
-/* default window dimensions (overwritten via -g option): */
+#ifdef _GENERAL_CONFIG
+
+/* enable external commands (defined below)? 0 = off, 1 = on:  */
+enum { EXT_COMMANDS = 0 };
+
+#endif
+#ifdef _WINDOW_CONFIG
+
+/* default window dimensions (overwritten via -g option):      */
 enum { WIN_WIDTH  = 800, WIN_HEIGHT = 600 };
 
-/* default color for window background:                   *
- * (see X(7) "COLOR NAMES" section for valid values)      */
+/* default color for window background:                        *
+ * (see X(7) "COLOR NAMES" section for valid values)           */
 static const char * const BG_COLOR  = "#999999";
-/* default color for thumbnail selection:                 */
+/* default color for thumbnail selection:                      */
 static const char * const SEL_COLOR = "#0066FF";
 
-/* how should images be scaled when they are loaded?:     *
- * (also controllable via -d/-s/-Z/-z options)            *
- *   SCALE_DOWN: 100%, but fit large images into window,  *
- *   SCALE_FIT:  fit all images into window,              *
- *   SCALE_ZOOM: use current zoom level, 100% at startup  */
+#endif
+#ifdef _IMAGE_CONFIG
+
+/* how should images be scaled when they are loaded?:          *
+ * (also controllable via -d/-s/-Z/-z options)                 *
+ *   SCALE_DOWN: 100%, but fit large images into window,       *
+ *   SCALE_FIT:  fit all images into window,                   *
+ *   SCALE_ZOOM: use current zoom level, 100% at startup       */
 static const scalemode_t SCALE_MODE = SCALE_DOWN;
 
-/* levels (percent) to use when zooming via '-' and '+':  */
+/* levels (percent) to use when zooming via '-' and '+':       */
 static const float zoom_levels[] = {
 	 12.5,  25.0,  50.0,  75.0,
 	100.0, 150.0, 200.0, 400.0, 800.0
 };
 
-/* default dimension of thumbnails (width == height):     */
+#endif
+#ifdef _THUMBS_CONFIG
+
+/* default dimension of thumbnails (width == height):          */
 enum { THUMB_SIZE = 60 };
 
-/* enable external commands (defined below)? 0=off, 1=on: */
-enum { EXT_COMMANDS = 0 };
+#endif
+#ifdef _MAPPINGS_CONFIG
 
-/* external commands and corresponding key mappings:      */
-static const command_t commands[] = {
-	/* ctrl-...  reload?  command, '#' is replaced by filename */
-	{  ',',      1,       "jpegtran -rotate 270 -copy all -outfile # #" },
-	{  '.',      1,       "jpegtran -rotate 90 -copy all -outfile # #" },
-	{  '<',      1,       "mogrify -rotate -90 #" },
-	{  '>',      1,       "mogrify -rotate +90 #" }
+/* keyboard mappings for image and thumbnail mode:             */
+static const keymap_t keys[] = {
+	/* key              function            argument */
+	{ XK_q,             quit,               None },
+	{ XK_r,             reload,             None },
+	{ XK_f,             toggle_fullscreen,  None },
+	{ XK_a,             toggle_antialias,   None },
+	{ XK_A,             toggle_alpha,       None },
+	{ XK_Return,        switch_mode,        None },
+
+	{ XK_g,             first,              None },
+	{ XK_G,             last,               None },
+	{ XK_n,             navigate,           +1 },
+	{ XK_space,         navigate,           +1 },
+	{ XK_p,             navigate,           -1 },
+	{ XK_BackSpace,     navigate,           -1 },
+	{ XK_bracketright,  navigate,           +10 },
+	{ XK_bracketleft,   navigate,           -10 },
+
+	{ XK_D,             remove_image,       None },
+
+	{ XK_h,             move,               DIR_LEFT },
+	{ XK_Left,          move,               DIR_LEFT },
+	{ XK_j,             move,               DIR_DOWN },
+	{ XK_Down,          move,               DIR_DOWN },
+	{ XK_k,             move,               DIR_UP },
+	{ XK_Up,            move,               DIR_UP },
+	{ XK_l,             move,               DIR_RIGHT },
+	{ XK_Right,         move,               DIR_RIGHT },
+
+	{ XK_braceleft,     scroll,             DIR_LEFT },
+	{ XK_Next,          scroll,             DIR_DOWN },
+	{ XK_Prior,         scroll,             DIR_UP },
+	{ XK_braceright,    scroll,             DIR_RIGHT },
+
+	{ XK_H,             pan_edge,           DIR_LEFT },
+	{ XK_J,             pan_edge,           DIR_DOWN },
+	{ XK_K,             pan_edge,           DIR_UP },
+	{ XK_L,             pan_edge,           DIR_RIGHT },
+
+	{ XK_plus,          zoom,               +1 },
+	{ XK_equal,         zoom,               +1 },
+	{ XK_KP_Add,        zoom,               +1 },
+	{ XK_minus,         zoom,               -1 },
+	{ XK_KP_Subtract,   zoom,               -1 },
+	{ XK_0,             zoom,               0 },
+	{ XK_KP_0,          zoom,               0 },
+	{ XK_w,             fit_to_win,         None },
+	{ XK_W,             fit_to_img,         None },
+
+	{ XK_less,          rotate,             DIR_LEFT },
+	{ XK_greater,       rotate,             DIR_RIGHT },
 };
+
+/* external commands and corresponding key mappings:           */
+static const command_t commands[] = {
+	/* ctrl-...    reload?  command, '#' is replaced by filename */
+	{ XK_comma,    True,    "jpegtran -rotate 270 -copy all -outfile # #" },
+	{ XK_period,   True,    "jpegtran -rotate 90 -copy all -outfile # #" },
+	{ XK_less,     True,    "mogrify -rotate -90 #" },
+	{ XK_greater,  True,    "mogrify -rotate +90 #" }
+};
+
+/* mouse button mappings for image mode:                       */
+static const button_t buttons[] = {
+	/* modifier     button       function    argument            */
+	{ None,         Button1,     navigate,   +1 },
+	{ None,         Button3,     navigate,   -1 },
+	{ None,         Button2,     drag,       None },
+	{ None,         Button4,     move,       DIR_UP },
+	{ None,         Button5,     move,       DIR_DOWN },
+	{ ShiftMask,    Button4,     move,       DIR_LEFT },
+	{ ShiftMask,    Button5,     move,       DIR_RIGHT },
+	{ ControlMask,  Button4,     zoom,       +1 },
+	{ ControlMask,  Button5,     zoom,       -1 },
+};
+
+#endif
diff --git a/events.c b/events.c
new file mode 100644
index 0000000..daa88ef
--- /dev/null
+++ b/events.c
@@ -0,0 +1,562 @@
+/* sxiv: events.c
+ * Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define _GENERAL_CONFIG
+#define _MAPPINGS_CONFIG
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <X11/keysym.h>
+#include <X11/Xutil.h>
+
+#include "events.h"
+#include "image.h"
+#include "thumbs.h"
+#include "types.h"
+#include "util.h"
+#include "window.h"
+#include "config.h"
+
+/* timeouts in milliseconds: */
+enum {
+	TO_WIN_RESIZE  = 75,
+	TO_IMAGE_DRAG  = 1,
+	TO_CURSOR_HIDE = 1500,
+	TO_THUMBS_LOAD = 200
+};
+
+void cleanup();
+void remove_file(int, unsigned char);
+void load_image(int);
+void update_title();
+
+extern appmode_t mode;
+extern img_t img;
+extern tns_t tns;
+extern win_t win;
+
+extern char **filenames;
+extern int filecnt, fileidx;
+
+int timo_cursor;
+int timo_redraw;
+unsigned char dragging;
+int mox, moy;
+
+int run_command(const char *cline, Bool reload) {
+	int fncnt, fnlen;
+	char *cn, *cmdline;
+	const char *co, *fname;
+	pid_t pid;
+	int ret, status;
+
+	if (!cline || !*cline)
+		return 0;
+
+	fncnt = 0;
+	co = cline - 1;
+	while ((co = strchr(co + 1, '#')))
+		fncnt++;
+
+	if (!fncnt)
+		return 0;
+
+	ret = 0;
+	fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel];
+	fnlen = strlen(fname);
+	cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) *
+	                                sizeof(char));
+
+	/* replace all '#' with filename */
+	for (co = cline; *co; co++) {
+		if (*co == '#') {
+			*cn++ = '"';
+			strcpy(cn, fname);
+			cn += fnlen;
+			*cn++ = '"';
+		} else {
+			*cn++ = *co;
+		}
+	}
+	*cn = '\0';
+
+	if ((pid = fork()) == 0) {
+		execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
+		warn("could not exec: /bin/sh");
+		exit(1);
+	} else if (pid < 0) {
+		warn("could not fork. command line was: %s", cmdline);
+	} else if (reload) {
+		waitpid(pid, &status, 0);
+		if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+			ret = 1;
+		else
+			warn("child exited with non-zero return value: %d. command line was: %s",
+			     WEXITSTATUS(status), cmdline);
+	}
+	
+	free(cmdline);
+	return ret;
+}
+
+void redraw() {
+	if (mode == MODE_NORMAL) {
+		img_render(&img, &win);
+		if (timo_cursor)
+			win_set_cursor(&win, CURSOR_ARROW);
+		else if (!dragging)
+			win_set_cursor(&win, CURSOR_NONE);
+	} else {
+		tns_render(&tns, &win);
+	}
+	update_title();
+	timo_redraw = 0;
+}
+
+void on_keypress(XEvent *ev) {
+	int i;
+	XKeyEvent *kev;
+	KeySym ksym;
+	char key;
+
+	if (!ev || ev->type != KeyPress)
+		return;
+	
+	kev = &ev->xkey;
+	XLookupString(kev, &key, 1, &ksym, NULL);
+
+	if (EXT_COMMANDS && (CLEANMASK(kev->state) & ControlMask)) {
+		for (i = 0; i < LEN(commands); i++) {
+			if (commands[i].ksym == ksym) {
+				win_set_cursor(&win, CURSOR_WATCH);
+				if (run_command(commands[i].cmdline, commands[i].reload)) {
+					if (mode == MODE_NORMAL) {
+						if (fileidx < tns.cnt)
+							tns_load(&tns, fileidx, filenames[fileidx], 1);
+						img_close(&img, 1);
+						load_image(fileidx);
+					} else {
+						if (!tns_load(&tns, tns.sel, filenames[tns.sel], 0)) {
+							remove_file(tns.sel, 0);
+							tns.dirty = 1;
+							if (tns.sel >= tns.cnt)
+								tns.sel = tns.cnt - 1;
+						}
+					}
+					redraw();
+				}
+				if (mode == MODE_THUMBS)
+					win_set_cursor(&win, CURSOR_ARROW);
+				else if (!timo_cursor)
+					win_set_cursor(&win, CURSOR_NONE);
+				return;
+			}
+		}
+	}
+
+	for (i = 0; i < LEN(keys); i++) {
+		if (ksym == keys[i].ksym && keys[i].handler) {
+			if (keys[i].handler(ev, keys[i].arg))
+				redraw();
+			return;
+		}
+	}
+}
+
+void on_buttonpress(XEvent *ev) {
+	int i, sel;
+	XButtonEvent *bev;
+
+	if (!ev || ev->type != ButtonPress)
+		return;
+
+	bev = &ev->xbutton;
+
+	if (mode == MODE_NORMAL) {
+		if (!dragging) {
+			win_set_cursor(&win, CURSOR_ARROW);
+			timo_cursor = TO_CURSOR_HIDE;
+		}
+
+		for (i = 0; i < LEN(buttons); i++) {
+			if (CLEANMASK(bev->state) == CLEANMASK(buttons[i].mod) &&
+			    bev->button == buttons[i].button && buttons[i].handler)
+			{
+				if (buttons[i].handler(ev, buttons[i].arg))
+					redraw();
+				return;
+			}
+		}
+	} else {
+		/* thumbnail mode */
+		switch (bev->button) {
+			case Button1:
+				if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
+					if (sel == tns.sel) {
+						load_image(tns.sel);
+						mode = MODE_NORMAL;
+						timo_cursor = TO_CURSOR_HIDE;
+					} else {
+						tns_highlight(&tns, &win, tns.sel, False);
+						tns_highlight(&tns, &win, sel, True);
+						tns.sel = sel;
+					}
+					redraw();
+					break;
+				}
+				break;
+			case Button4:
+			case Button5:
+				if (tns_scroll(&tns, bev->button == Button4 ? DIR_UP : DIR_DOWN))
+					redraw();
+				break;
+		}
+	}
+}
+
+void on_motionnotify(XEvent *ev) {
+	XMotionEvent *mev;
+
+	if (!ev || ev->type != MotionNotify)
+		return;
+
+	mev = &ev->xmotion;
+
+	if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
+		if (img_move(&img, &win, mev->x - mox, mev->y - moy))
+			timo_redraw = TO_IMAGE_DRAG;
+		mox = mev->x;
+		moy = mev->y;
+	}
+}
+
+void run() {
+	int xfd, timeout;
+	fd_set fds;
+	struct timeval tt, t0, t1;
+	XEvent ev;
+
+	dragging = 0;
+	timo_cursor = mode == MODE_NORMAL ? TO_CURSOR_HIDE : 0;
+
+	redraw();
+
+	while (1) {
+		if (mode == MODE_THUMBS && tns.cnt < filecnt) {
+			/* load thumbnails */
+			win_set_cursor(&win, CURSOR_WATCH);
+			gettimeofday(&t0, 0);
+
+			while (tns.cnt < filecnt && !XPending(win.env.dpy)) {
+				if (tns_load(&tns, tns.cnt, filenames[tns.cnt], 0))
+					tns.cnt++;
+				else
+					remove_file(tns.cnt, 0);
+				gettimeofday(&t1, 0);
+				if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD)
+					break;
+			}
+			if (tns.cnt == filecnt)
+				win_set_cursor(&win, CURSOR_ARROW);
+			if (!XPending(win.env.dpy)) {
+				redraw();
+				continue;
+			} else {
+				timo_redraw = TO_THUMBS_LOAD;
+			}
+		} else if (timo_cursor || timo_redraw) {
+			/* check active timeouts */
+			gettimeofday(&t0, 0);
+			timeout = MIN(timo_cursor + 1, timo_redraw + 1);
+			MSEC_TO_TIMEVAL(timeout, &tt);
+			xfd = ConnectionNumber(win.env.dpy);
+			FD_ZERO(&fds);
+			FD_SET(xfd, &fds);
+
+			if (!XPending(win.env.dpy))
+				select(xfd + 1, &fds, 0, 0, &tt);
+			gettimeofday(&t1, 0);
+			timeout = MIN(TIMEDIFF(&t1, &t0), timeout);
+
+			/* timeouts fired? */
+			if (timo_cursor) {
+				timo_cursor = MAX(0, timo_cursor - timeout);
+				if (!timo_cursor)
+					win_set_cursor(&win, CURSOR_NONE);
+			}
+			if (timo_redraw) {
+				timo_redraw = MAX(0, timo_redraw - timeout);
+				if (!timo_redraw)
+					redraw();
+			}
+			if ((timo_cursor || timo_redraw) && !XPending(win.env.dpy))
+				continue;
+		}
+
+		if (!XNextEvent(win.env.dpy, &ev)) {
+			switch (ev.type) {
+				case ButtonPress:
+					on_buttonpress(&ev);
+					break;
+				case ButtonRelease:
+					if (dragging) {
+						dragging = 0;
+						if (mode == MODE_NORMAL) {
+							win_set_cursor(&win, CURSOR_ARROW);
+							timo_cursor = TO_CURSOR_HIDE;
+						}
+					}
+					break;
+				case ClientMessage:
+					if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
+						return;
+					break;
+				case ConfigureNotify:
+					if (win_configure(&win, &ev.xconfigure)) {
+						timo_redraw = TO_WIN_RESIZE;
+						if (mode == MODE_NORMAL)
+							img.checkpan = 1;
+						else
+							tns.dirty = 1;
+					}
+					break;
+				case KeyPress:
+					on_keypress(&ev);
+					break;
+				case MotionNotify:
+					if (dragging) {
+						on_motionnotify(&ev);
+					} else if (mode == MODE_NORMAL) {
+						if (!timo_cursor)
+							win_set_cursor(&win, CURSOR_ARROW);
+						timo_cursor = TO_CURSOR_HIDE;
+					}
+					break;
+			}
+		}
+	}
+}
+
+
+/* handler functions for key and button mappings: */
+
+int quit(XEvent *e, arg_t a) {
+	cleanup();
+	exit(0);
+}
+
+int reload(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		load_image(fileidx);
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int toggle_fullscreen(XEvent *e, arg_t a) {
+	win_toggle_fullscreen(&win);
+	if (mode == MODE_NORMAL)
+		img.checkpan = 1;
+	else
+		tns.dirty = 1;
+	timo_redraw = TO_WIN_RESIZE;
+	return 0;
+}
+
+int toggle_antialias(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		img_toggle_antialias(&img);
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int toggle_alpha(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		img.alpha ^= 1;
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int switch_mode(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		if (!tns.thumbs)
+			tns_init(&tns, filecnt);
+		img_close(&img, 0);
+		win_set_cursor(&win, CURSOR_ARROW);
+		timo_cursor = 0;
+		tns.sel = fileidx;
+		tns.dirty = 1;
+		mode = MODE_THUMBS;
+	} else {
+		timo_cursor = TO_CURSOR_HIDE;
+		load_image(tns.sel);
+		mode = MODE_NORMAL;
+	}
+	return 1;
+}
+
+int navigate(XEvent *e, arg_t n) {
+	if (mode == MODE_NORMAL) {
+		n += fileidx;
+		if (n < 0)
+			n = 0;
+		if (n >= filecnt)
+			n = filecnt - 1;
+
+		if (n != fileidx) {
+			load_image(n);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int first(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL && fileidx != 0) {
+		load_image(0);
+		return 1;
+	} else if (mode == MODE_THUMBS && tns.sel != 0) {
+		tns.sel = 0;
+		tns.dirty = 1;
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int last(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL && fileidx != filecnt - 1) {
+		load_image(filecnt - 1);
+		return 1;
+	} else if (mode == MODE_THUMBS && tns.sel != tns.cnt - 1) {
+		tns.sel = tns.cnt - 1;
+		tns.dirty = 1;
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int remove_image(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		remove_file(fileidx, 1);
+		load_image(fileidx >= filecnt ? filecnt - 1 : fileidx);
+		return 1;
+	} else if (tns.sel < tns.cnt) {
+		remove_file(tns.sel, 1);
+		tns.dirty = 1;
+		if (tns.sel >= tns.cnt)
+			tns.sel = tns.cnt - 1;
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+int move(XEvent *e, arg_t dir) {
+	if (mode == MODE_NORMAL)
+		return img_pan(&img, &win, dir, 0);
+	else
+		return tns_move_selection(&tns, &win, dir);
+}
+
+int scroll(XEvent *e, arg_t dir) {
+	if (mode == MODE_NORMAL)
+		return img_pan(&img, &win, dir, 1);
+	else
+		return 0;
+}
+
+int pan_edge(XEvent *e, arg_t dir) {
+	if (mode == MODE_NORMAL)
+		return img_pan_edge(&img, &win, dir);
+	else
+		return 0;
+}
+
+int drag(XEvent *e, arg_t a) {
+	if (mode == MODE_NORMAL) {
+		mox = e->xbutton.x;
+		moy = e->xbutton.y;
+		win_set_cursor(&win, CURSOR_HAND);
+		timo_cursor = 0;
+		dragging = 1;
+	}
+	return 0;
+}
+
+int rotate(XEvent *e, arg_t dir) {
+	if (mode == MODE_NORMAL) {
+		if (dir == DIR_LEFT) {
+			img_rotate_left(&img, &win);
+			return 1;
+		} else if (dir == DIR_RIGHT) {
+			img_rotate_right(&img, &win);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int zoom(XEvent *e, arg_t scale) {
+	if (mode != MODE_NORMAL)
+		return 0;
+	if (scale > 0)
+		return img_zoom_in(&img, &win);
+	else if (scale < 0)
+		return img_zoom_out(&img, &win);
+	else
+		return img_zoom(&img, &win, 1.0);
+}
+
+int fit_to_win(XEvent *e, arg_t ret) {
+	if (mode == MODE_NORMAL) {
+		if ((ret = img_fit_win(&img, &win)))
+			img_center(&img, &win);
+		return ret;
+	} else {
+		return 0;
+	}
+}
+
+int fit_to_img(XEvent *e, arg_t ret) {
+	int x, y;
+	unsigned int w, h;
+
+	if (mode == MODE_NORMAL) {
+		x = MAX(0, win.x + img.x);
+		y = MAX(0, win.y + img.y);
+		w = img.w * img.zoom;
+		h = img.h * img.zoom;
+		if ((ret = win_moveresize(&win, x, y, w, h))) {
+			img.x = x - win.x;
+			img.y = y - win.y;
+		}
+		return ret;
+	} else {
+		return 0;
+	}
+}
diff --git a/events.h b/events.h
new file mode 100644
index 0000000..d5ddca7
--- /dev/null
+++ b/events.h
@@ -0,0 +1,67 @@
+/* sxiv: events.h
+ * Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef EVENTS_H
+#define EVENTS_H
+
+#include <X11/Xlib.h>
+
+typedef struct {
+	KeySym ksym;
+	Bool reload;
+	const char *cmdline;
+} command_t;
+
+typedef int arg_t;
+
+typedef struct {
+	KeySym ksym;
+	int (*handler)(XEvent*, arg_t);
+	arg_t arg;
+} keymap_t;
+
+typedef struct {
+	unsigned int mod;
+	unsigned int button;
+	int (*handler)(XEvent*, arg_t);
+	arg_t arg;
+} button_t;
+
+void run();
+
+/* handler functions for key and button mappings: */
+int quit(XEvent*, arg_t);
+int reload(XEvent*, arg_t);
+int toggle_fullscreen(XEvent*, arg_t);
+int toggle_antialias(XEvent*, arg_t);
+int toggle_alpha(XEvent*, arg_t);
+int switch_mode(XEvent*, arg_t);
+int navigate(XEvent*, arg_t);
+int first(XEvent*, arg_t);
+int last(XEvent*, arg_t);
+int remove_image(XEvent*, arg_t);
+int move(XEvent*, arg_t);
+int scroll(XEvent*, arg_t);
+int pan_edge(XEvent*, arg_t);
+int drag(XEvent*, arg_t);
+int rotate(XEvent*, arg_t);
+int zoom(XEvent*, arg_t);
+int fit_to_win(XEvent*, arg_t);
+int fit_to_img(XEvent*, arg_t);
+
+#endif /* EVENTS_H */
diff --git a/image.c b/image.c
index 8a82f32..d03e3c7 100644
--- a/image.c
+++ b/image.c
@@ -16,6 +16,8 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
+#define _IMAGE_CONFIG
+
 #include <unistd.h>
 
 #include "image.h"
@@ -238,7 +240,7 @@ int img_zoom_in(img_t *img, win_t *win) {
 	if (!img || !img->im || !win)
 		return 0;
 
-	for (i = 1; i < zl_cnt; ++i) {
+	for (i = 1; i < zl_cnt; i++) {
 		if (zoom_levels[i] > img->zoom * 100.0)
 			return img_zoom(img, win, zoom_levels[i] / 100.0);
 	}
@@ -251,7 +253,7 @@ int img_zoom_out(img_t *img, win_t *win) {
 	if (!img || !img->im || !win)
 		return 0;
 
-	for (i = zl_cnt - 2; i >= 0; --i) {
+	for (i = zl_cnt - 2; i >= 0; i--) {
 		if (zoom_levels[i] < img->zoom * 100.0)
 			return img_zoom(img, win, zoom_levels[i] / 100.0);
 	}
diff --git a/main.c b/main.c
index 2a80aff..1b98efd 100644
--- a/main.c
+++ b/main.c
@@ -16,32 +16,22 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-#define _XOPEN_SOURCE 700
-
 #include <stdlib.h>
-#include <stdio.h>
 #include <string.h>
-#include <sys/select.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
 #include <unistd.h>
+#include <sys/stat.h>
 
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/keysym.h>
-
+#include "events.h"
 #include "image.h"
 #include "options.h"
 #include "thumbs.h"
-#include "types.h"
 #include "util.h"
 #include "window.h"
-#include "config.h"
 
-enum { TITLE_LEN = 256, FNAME_CNT = 1024 };
-
-void run();
+enum {
+	TITLE_LEN = 256,
+	FNAME_CNT = 1024
+};
 
 appmode_t mode;
 img_t img;
@@ -64,6 +54,23 @@ void cleanup() {
 	}
 }
 
+int check_add_file(char *filename) {
+	if (!filename)
+		return 0;
+
+	if (access(filename, R_OK)) {
+		warn("could not open file: %s", filename);
+		return 0;
+	} else {
+		if (fileidx == filecnt) {
+			filecnt *= 2;
+			filenames = (char**) s_realloc(filenames, filecnt * sizeof(char*));
+		}
+		filenames[fileidx++] = filename;
+		return 1;
+	}
+}
+
 void remove_file(int n, unsigned char silent) {
 	if (n < 0 || n >= filecnt)
 		return;
@@ -84,32 +91,32 @@ void remove_file(int n, unsigned char silent) {
 		memset(tns.thumbs + tns.cnt - 1, 0, sizeof(thumb_t));
 	}
 
-	--filecnt;
+	filecnt--;
 	if (n < tns.cnt)
-		--tns.cnt;
+		tns.cnt--;
 }
 
-int load_image(int new) {
+void load_image(int new) {
 	struct stat fstats;
 
-	if (new >= 0 && new < filecnt) {
-		win_set_cursor(&win, CURSOR_WATCH);
-		img_close(&img, 0);
-		
-		while (!img_load(&img, filenames[new])) {
-			remove_file(new, 0);
-			if (new >= filecnt)
-				new = filecnt - 1;
-		}
-		fileidx = new;
-		if (!stat(filenames[new], &fstats))
-			filesize = fstats.st_size;
-		else
-			filesize = 0;
+	if (new < 0 || new >= filecnt)
+		return;
 
-		/* cursor is reset in redraw() */
+	/* cursor is reset in redraw() */
+	win_set_cursor(&win, CURSOR_WATCH);
+	img_close(&img, 0);
+		
+	while (!img_load(&img, filenames[new])) {
+		remove_file(new, 0);
+		if (new >= filecnt)
+			new = filecnt - 1;
 	}
-	return 1;
+
+	fileidx = new;
+	if (!stat(filenames[new], &fstats))
+		filesize = fstats.st_size;
+	else
+		filesize = 0;
 }
 
 void update_title() {
@@ -138,23 +145,6 @@ void update_title() {
 	win_set_title(&win, win_title);
 }
 
-int check_append(char *filename) {
-	if (!filename)
-		return 0;
-
-	if (access(filename, R_OK)) {
-		warn("could not open file: %s", filename);
-		return 0;
-	} else {
-		if (fileidx == filecnt) {
-			filecnt *= 2;
-			filenames = (char**) s_realloc(filenames, filecnt * sizeof(char*));
-		}
-		filenames[fileidx++] = filename;
-		return 1;
-	}
-}
-
 int fncmp(const void *a, const void *b) {
 	return strcoll(*((char* const*) a), *((char* const*) b));
 }
@@ -187,20 +177,21 @@ int main(int argc, char **argv) {
 	filenames = (char**) s_malloc(filecnt * sizeof(char*));
 	fileidx = 0;
 
+	/* build file list: */
 	if (options->from_stdin) {
 		while ((len = getline(&filename, &n, stdin)) > 0) {
 			if (filename[len-1] == '\n')
 				filename[len-1] = '\0';
-			if (!*filename || !check_append(filename))
+			if (!*filename || !check_add_file(filename))
 				free(filename);
 			filename = NULL;
 		}
 	} else {
-		for (i = 0; i < options->filecnt; ++i) {
+		for (i = 0; i < options->filecnt; i++) {
 			filename = options->filenames[i];
 
 			if (stat(filename, &fstats) || !S_ISDIR(fstats.st_mode)) {
-				check_append(filename);
+				check_add_file(filename);
 			} else {
 				if (!options->recursive) {
 					warn("ignoring directory: %s", filename);
@@ -212,7 +203,7 @@ int main(int argc, char **argv) {
 				}
 				start = fileidx;
 				while ((filename = r_readdir(&dir))) {
-					if (!check_append(filename))
+					if (!check_add_file(filename))
 						free((void*) filename);
 				}
 				r_closedir(&dir);
@@ -252,550 +243,3 @@ int main(int argc, char **argv) {
 
 	return 0;
 }
-
-int run_command(const char *cline, Bool reload) {
-	int fncnt, fnlen;
-	char *cn, *cmdline;
-	const char *co, *fname;
-	pid_t pid;
-	int ret, status;
-
-	if (!cline || !*cline)
-		return 0;
-
-	fncnt = 0;
-	co = cline - 1;
-	while ((co = strchr(co + 1, '#')))
-		++fncnt;
-
-	if (!fncnt)
-		return 0;
-
-	ret = 0;
-	fname = filenames[mode == MODE_NORMAL ? fileidx : tns.sel];
-	fnlen = strlen(fname);
-	cn = cmdline = (char*) s_malloc((strlen(cline) + fncnt * (fnlen + 2)) *
-	                                sizeof(char));
-
-	/* replace all '#' with filename */
-	for (co = cline; *co; ++co) {
-		if (*co == '#') {
-			*cn++ = '"';
-			strcpy(cn, fname);
-			cn += fnlen;
-			*cn++ = '"';
-		} else {
-			*cn++ = *co;
-		}
-	}
-	*cn = '\0';
-
-	if ((pid = fork()) == 0) {
-		execlp("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
-		warn("could not exec: /bin/sh");
-		exit(1);
-	} else if (pid < 0) {
-		warn("could not fork. command line was: %s", cmdline);
-	} else if (reload) {
-		waitpid(pid, &status, 0);
-		if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
-			ret = 1;
-		else
-			warn("child exited with non-zero return value: %d. command line was: %s",
-			     WEXITSTATUS(status), cmdline);
-	}
-	
-	free(cmdline);
-	return ret;
-}
-
-
-/* event handling */
-
-/* timeouts in milliseconds: */
-enum {
-	TO_WIN_RESIZE  = 75,
-	TO_IMAGE_DRAG  = 1,
-	TO_CURSOR_HIDE = 1500,
-	TO_THUMBS_LOAD = 200
-};
-
-int timo_cursor;
-int timo_redraw;
-unsigned char drag;
-int mox, moy;
-
-void redraw() {
-	if (mode == MODE_NORMAL) {
-		img_render(&img, &win);
-		if (timo_cursor)
-			win_set_cursor(&win, CURSOR_ARROW);
-		else if (!drag)
-			win_set_cursor(&win, CURSOR_NONE);
-	} else {
-		tns_render(&tns, &win);
-	}
-	update_title();
-	timo_redraw = 0;
-}
-
-void on_keypress(XKeyEvent *kev) {
-	int x, y;
-	unsigned int w, h;
-	char key;
-	KeySym ksym;
-	int changed, ctrl;
-
-	if (!kev)
-		return;
-	
-	XLookupString(kev, &key, 1, &ksym, NULL);
-	changed = 0;
-	ctrl = CLEANMASK(kev->state) & ControlMask;
-
-	/* external commands from commands.h */
-	if (EXT_COMMANDS && ctrl) {
-		for (x = 0; x < LEN(commands); ++x) {
-			if (commands[x].key == key) {
-				win_set_cursor(&win, CURSOR_WATCH);
-				if (run_command(commands[x].cmdline, commands[x].reload)) {
-					if (mode == MODE_NORMAL) {
-						if (fileidx < tns.cnt)
-							tns_load(&tns, fileidx, filenames[fileidx], 1);
-						img_close(&img, 1);
-						load_image(fileidx);
-					} else {
-						if (!tns_load(&tns, tns.sel, filenames[tns.sel], 0)) {
-							remove_file(tns.sel, 0);
-							tns.dirty = 1;
-							if (tns.sel >= tns.cnt)
-								tns.sel = tns.cnt - 1;
-						}
-					}
-					redraw();
-				}
-				if (mode == MODE_THUMBS)
-					win_set_cursor(&win, CURSOR_ARROW);
-				else if (!timo_cursor)
-					win_set_cursor(&win, CURSOR_NONE);
-				return;
-			}
-		}
-	}
-
-	if (mode == MODE_NORMAL) {
-		switch (ksym) {
-			/* navigate image list */
-			case XK_n:
-			case XK_space:
-				if (fileidx + 1 < filecnt)
-					changed = load_image(fileidx + 1);
-				break;
-			case XK_p:
-			case XK_BackSpace:
-				if (fileidx > 0)
-					changed = load_image(fileidx - 1);
-				break;
-			case XK_bracketleft:
-				if (fileidx != 0)
-					changed = load_image(MAX(0, fileidx - 10));
-				break;
-			case XK_bracketright:
-				if (fileidx != filecnt - 1)
-					changed = load_image(MIN(fileidx + 10, filecnt - 1));
-				break;
-			case XK_g:
-				if (fileidx != 0)
-					changed = load_image(0);
-				break;
-			case XK_G:
-				if (fileidx != filecnt - 1)
-					changed = load_image(filecnt - 1);
-				break;
-
-			/* zooming */
-			case XK_plus:
-			case XK_equal:
-			case XK_KP_Add:
-				changed = img_zoom_in(&img, &win);
-				break;
-			case XK_minus:
-			case XK_KP_Subtract:
-				changed = img_zoom_out(&img, &win);
-				break;
-			case XK_0:
-			case XK_KP_0:
-				changed = img_zoom(&img, &win, 1.0);
-				break;
-			case XK_w:
-				if ((changed = img_fit_win(&img, &win)))
-					img_center(&img, &win);
-				break;
-
-			/* panning */
-			case XK_h:
-			case XK_Left:
-				changed = img_pan(&img, &win, DIR_LEFT, ctrl);
-				break;
-			case XK_j:
-			case XK_Down:
-				changed = img_pan(&img, &win, DIR_DOWN, ctrl);
-				break;
-			case XK_k:
-			case XK_Up:
-				changed = img_pan(&img, &win, DIR_UP, ctrl);
-				break;
-			case XK_l:
-			case XK_Right:
-				changed = img_pan(&img, &win, DIR_RIGHT, ctrl);
-				break;
-			case XK_Prior:
-				changed = img_pan(&img, &win, DIR_UP, 1);
-				break;
-			case XK_Next:
-				changed = img_pan(&img, &win, DIR_DOWN, 1);
-				break;
-
-			case XK_H:
-				changed = img_pan_edge(&img, &win, DIR_LEFT);
-				break;
-			case XK_J:
-				changed = img_pan_edge(&img, &win, DIR_DOWN);
-				break;
-			case XK_K:
-				changed = img_pan_edge(&img, &win, DIR_UP);
-				break;
-			case XK_L:
-				changed = img_pan_edge(&img, &win, DIR_RIGHT);
-				break;
-
-			/* rotation */
-			case XK_less:
-				img_rotate_left(&img, &win);
-				changed = 1;
-				break;
-			case XK_greater:
-				img_rotate_right(&img, &win);
-				changed = 1;
-				break;
-
-			/* control window */
-			case XK_W:
-				x = MAX(0, win.x + img.x);
-				y = MAX(0, win.y + img.y);
-				w = img.w * img.zoom;
-				h = img.h * img.zoom;
-				if ((changed = win_moveresize(&win, x, y, w, h))) {
-					img.x = x - win.x;
-					img.y = y - win.y;
-				}
-				break;
-
-			/* switch to thumbnail mode */
-			case XK_Return:
-				if (!tns.thumbs)
-					tns_init(&tns, filecnt);
-				img_close(&img, 0);
-				mode = MODE_THUMBS;
-				win_set_cursor(&win, CURSOR_ARROW);
-				timo_cursor = 0;
-				tns.sel = fileidx;
-				changed = tns.dirty = 1;
-				break;
-
-			/* miscellaneous */
-			case XK_a:
-				img_toggle_antialias(&img);
-				changed = 1;
-				break;
-			case XK_A:
-				img.alpha ^= 1;
-				changed = 1;
-				break;
-			case XK_D:
-				remove_file(fileidx, 1);
-				changed = load_image(fileidx >= filecnt ? filecnt - 1 : fileidx);
-				break;
-			case XK_r:
-				changed = load_image(fileidx);
-				break;
-		}
-	} else {
-		/* thumbnail mode */
-		switch (ksym) {
-			/* open selected image */
-			case XK_Return:
-				load_image(tns.sel);
-				mode = MODE_NORMAL;
-				changed = 1;
-				break;
-
-			/* move selection */
-			case XK_h:
-			case XK_Left:
-				changed = tns_move_selection(&tns, &win, DIR_LEFT);
-				break;
-			case XK_j:
-			case XK_Down:
-				changed = tns_move_selection(&tns, &win, DIR_DOWN);
-				break;
-			case XK_k:
-			case XK_Up:
-				changed = tns_move_selection(&tns, &win, DIR_UP);
-				break;
-			case XK_l:
-			case XK_Right:
-				changed = tns_move_selection(&tns, &win, DIR_RIGHT);
-				break;
-			case XK_g:
-				if (tns.sel != 0) {
-					tns.sel = 0;
-					changed = tns.dirty = 1;
-				}
-				break;
-			case XK_G:
-				if (tns.sel != tns.cnt - 1) {
-					tns.sel = tns.cnt - 1;
-					changed = tns.dirty = 1;
-				}
-				break;
-
-			/* miscellaneous */
-			case XK_D:
-				if (tns.sel < tns.cnt) {
-					remove_file(tns.sel, 1);
-					changed = tns.dirty = 1;
-					if (tns.sel >= tns.cnt)
-						tns.sel = tns.cnt - 1;
-				}
-				break;
-		}
-	}
-
-	/* common key mappings */
-	switch (ksym) {
-		case XK_q:
-			cleanup();
-			exit(0);
-		case XK_f:
-			win_toggle_fullscreen(&win);
-			if (mode == MODE_NORMAL)
-				img.checkpan = 1;
-			else
-				tns.dirty = 1;
-			timo_redraw = TO_WIN_RESIZE;
-			break;
-	}
-
-	if (changed)
-		redraw();
-}
-
-void on_buttonpress(XButtonEvent *bev) {
-	int changed, sel;
-	unsigned int mask;
-
-	if (!bev)
-		return;
-
-	mask = CLEANMASK(bev->state);
-	changed = 0;
-
-	if (mode == MODE_NORMAL) {
-		if (!drag) {
-			win_set_cursor(&win, CURSOR_ARROW);
-			timo_cursor = TO_CURSOR_HIDE;
-		}
-
-		switch (bev->button) {
-			case Button1:
-				if (fileidx + 1 < filecnt)
-					changed = load_image(fileidx + 1);
-				break;
-			case Button2:
-				mox = bev->x;
-				moy = bev->y;
-				win_set_cursor(&win, CURSOR_HAND);
-				timo_cursor = 0;
-				drag = 1;
-				break;
-			case Button3:
-				if (fileidx > 0)
-					changed = load_image(fileidx - 1);
-				break;
-			case Button4:
-				if (mask == ControlMask)
-					changed = img_zoom_in(&img, &win);
-				else if (mask == ShiftMask)
-					changed = img_pan(&img, &win, DIR_LEFT, 0);
-				else
-					changed = img_pan(&img, &win, DIR_UP, 0);
-				break;
-			case Button5:
-				if (mask == ControlMask)
-					changed = img_zoom_out(&img, &win);
-				else if (mask == ShiftMask)
-					changed = img_pan(&img, &win, DIR_RIGHT, 0);
-				else
-					changed = img_pan(&img, &win, DIR_DOWN, 0);
-				break;
-			case 6:
-				changed = img_pan(&img, &win, DIR_LEFT, 0);
-				break;
-			case 7:
-				changed = img_pan(&img, &win, DIR_RIGHT, 0);
-				break;
-		}
-	} else {
-		/* thumbnail mode */
-		switch (bev->button) {
-			case Button1:
-				if ((sel = tns_translate(&tns, bev->x, bev->y)) >= 0) {
-					if (sel == tns.sel) {
-						load_image(tns.sel);
-						mode = MODE_NORMAL;
-						timo_cursor = TO_CURSOR_HIDE;
-					} else {
-						tns_highlight(&tns, &win, tns.sel, False);
-						tns_highlight(&tns, &win, sel, True);
-						tns.sel = sel;
-					}
-					changed = 1;
-					break;
-				}
-				break;
-			case Button4:
-				changed = tns_scroll(&tns, DIR_UP);
-				break;
-			case Button5:
-				changed = tns_scroll(&tns, DIR_DOWN);
-				break;
-		}
-	}
-
-	if (changed)
-		redraw();
-}
-
-void on_motionnotify(XMotionEvent *mev) {
-	if (!mev)
-		return;
-
-	if (mev->x >= 0 && mev->x <= win.w && mev->y >= 0 && mev->y <= win.h) {
-		if (img_move(&img, &win, mev->x - mox, mev->y - moy))
-			timo_redraw = TO_IMAGE_DRAG;
-
-		mox = mev->x;
-		moy = mev->y;
-	}
-}
-
-void run() {
-	int xfd, timeout;
-	fd_set fds;
-	struct timeval tt, t0, t1;
-	XEvent ev;
-
-	drag = 0;
-	timo_cursor = mode == MODE_NORMAL ? TO_CURSOR_HIDE : 0;
-
-	redraw();
-
-	while (1) {
-		if (mode == MODE_THUMBS && tns.cnt < filecnt) {
-			win_set_cursor(&win, CURSOR_WATCH);
-			gettimeofday(&t0, 0);
-
-			while (tns.cnt < filecnt && !XPending(win.env.dpy)) {
-				if (tns_load(&tns, tns.cnt, filenames[tns.cnt], 0))
-					++tns.cnt;
-				else
-					remove_file(tns.cnt, 0);
-				gettimeofday(&t1, 0);
-				if (TIMEDIFF(&t1, &t0) >= TO_THUMBS_LOAD)
-					break;
-			}
-			if (tns.cnt == filecnt)
-				win_set_cursor(&win, CURSOR_ARROW);
-			if (!XPending(win.env.dpy)) {
-				redraw();
-				continue;
-			} else {
-				timo_redraw = TO_THUMBS_LOAD;
-			}
-		} else if (timo_cursor || timo_redraw) {
-			gettimeofday(&t0, 0);
-			if (timo_cursor && timo_redraw)
-				timeout = MIN(timo_cursor, timo_redraw);
-			else if (timo_cursor)
-				timeout = timo_cursor;
-			else
-				timeout = timo_redraw;
-			MSEC_TO_TIMEVAL(timeout, &tt);
-			xfd = ConnectionNumber(win.env.dpy);
-			FD_ZERO(&fds);
-			FD_SET(xfd, &fds);
-
-			if (!XPending(win.env.dpy))
-				select(xfd + 1, &fds, 0, 0, &tt);
-			gettimeofday(&t1, 0);
-			timeout = MIN(TIMEDIFF(&t1, &t0), timeout);
-
-			/* timeouts fired? */
-			if (timo_cursor) {
-				timo_cursor = MAX(0, timo_cursor - timeout);
-				if (!timo_cursor)
-					win_set_cursor(&win, CURSOR_NONE);
-			}
-			if (timo_redraw) {
-				timo_redraw = MAX(0, timo_redraw - timeout);
-				if (!timo_redraw)
-					redraw();
-			}
-			if (!XPending(win.env.dpy) && (timo_cursor || timo_redraw))
-				continue;
-		}
-
-		if (!XNextEvent(win.env.dpy, &ev)) {
-			switch (ev.type) {
-				case KeyPress:
-					on_keypress(&ev.xkey);
-					break;
-				case ButtonPress:
-					on_buttonpress(&ev.xbutton);
-					break;
-				case ButtonRelease:
-					if (ev.xbutton.button == Button2) {
-						drag = 0;
-						if (mode == MODE_NORMAL) {
-							win_set_cursor(&win, CURSOR_ARROW);
-							timo_cursor = TO_CURSOR_HIDE;
-						}
-					}
-					break;
-				case MotionNotify:
-					if (drag) {
-						on_motionnotify(&ev.xmotion);
-					} else if (mode == MODE_NORMAL) {
-						if (!timo_cursor)
-							win_set_cursor(&win, CURSOR_ARROW);
-						timo_cursor = TO_CURSOR_HIDE;
-					}
-					break;
-				case ConfigureNotify:
-					if (win_configure(&win, &ev.xconfigure)) {
-						timo_redraw = TO_WIN_RESIZE;
-						if (mode == MODE_NORMAL)
-							img.checkpan = 1;
-						else
-							tns.dirty = 1;
-					}
-					break;
-				case ClientMessage:
-					if ((Atom) ev.xclient.data.l[0] == wm_delete_win)
-						return;
-					break;
-			}
-		}
-	}
-}
diff --git a/options.c b/options.c
index 03c6194..3659601 100644
--- a/options.c
+++ b/options.c
@@ -17,6 +17,7 @@
  */
 
 #define _XOPEN_SOURCE
+#define _IMAGE_CONFIG
 
 #include <stdlib.h>
 #include <string.h>
diff --git a/sxiv.1 b/sxiv.1
index b3e763c..c27dfe7 100644
--- a/sxiv.1
+++ b/sxiv.1
@@ -150,16 +150,16 @@ Pan to top image edge.
 .B L
 Pan to right image edge.
 .TP
-.BR Ctrl-h ", " Ctrl-Left
+.BR {
 Pan image one window width left.
 .TP
-.BR Ctrl-j ", " Ctrl-Down ", " PageDn
+.BR PageDn
 Pan image one window height down.
 .TP
-.BR Ctrl-k ", " Ctrl-Up ", " PageUp
+.BR PageUp
 Pan image one window height up.
 .TP
-.BR Ctrl-l ", " Ctrl-Right
+.BR }
 Pan image one window width right.
 .SS Rotation
 .TP
diff --git a/thumbs.c b/thumbs.c
index dd1dba6..5700b87 100644
--- a/thumbs.c
+++ b/thumbs.c
@@ -16,6 +16,8 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
+#define _THUMBS_CONFIG
+
 #include <stdlib.h>
 #include <string.h>
 #include <sys/time.h>
@@ -205,7 +207,7 @@ void tns_free(tns_t *tns) {
 		return;
 
 	if (tns->thumbs) {
-		for (i = 0; i < tns->cnt; ++i) {
+		for (i = 0; i < tns->cnt; i++) {
 			if (tns->thumbs[i].im) {
 				imlib_context_set_image(tns->thumbs[i].im);
 				imlib_free_image();
@@ -337,7 +339,7 @@ void tns_render(tns_t *tns, win_t *win) {
 	tns->x = x = (win->w - MIN(cnt, tns->cols) * thumb_dim) / 2 + 5;
 	tns->y = y = (win->h - (cnt / tns->cols + r) * thumb_dim) / 2 + 5;
 
-	for (i = 0; i < cnt; ++i) {
+	for (i = 0; i < cnt; i++) {
 		t = &tns->thumbs[tns->first + i];
 		t->x = x + (THUMB_SIZE - t->w) / 2;
 		t->y = y + (THUMB_SIZE - t->h) / 2;
@@ -391,11 +393,11 @@ int tns_move_selection(tns_t *tns, win_t *win, direction_t dir) {
 	switch (dir) {
 		case DIR_LEFT:
 			if (tns->sel > 0)
-				--tns->sel;
+				tns->sel--;
 			break;
 		case DIR_RIGHT:
 			if (tns->sel < tns->cnt - 1)
-				++tns->sel;
+				tns->sel++;
 			break;
 		case DIR_UP:
 			if (tns->sel >= tns->cols)
diff --git a/types.h b/types.h
index 0726286..de6e04d 100644
--- a/types.h
+++ b/types.h
@@ -2,31 +2,25 @@
 #define TYPES_H
 
 typedef enum {
-	MODE_NORMAL = 0,
+	MODE_NORMAL,
 	MODE_THUMBS
 } appmode_t;
 
-typedef struct {
-	char key;
-	int reload;
-	const char *cmdline;
-} command_t;
-
 typedef enum {
-	DIR_LEFT = 0,
+	DIR_LEFT,
 	DIR_RIGHT,
 	DIR_UP,
 	DIR_DOWN
 } direction_t;
 
 typedef enum {
-	SCALE_DOWN = 0,
+	SCALE_DOWN,
 	SCALE_FIT,
 	SCALE_ZOOM
 } scalemode_t;
 
 typedef enum {
-	CURSOR_ARROW = 0,
+	CURSOR_ARROW,
 	CURSOR_NONE,
 	CURSOR_HAND,
 	CURSOR_WATCH
diff --git a/util.c b/util.c
index 3957629..dee497d 100644
--- a/util.c
+++ b/util.c
@@ -26,8 +26,10 @@
 #include "options.h"
 #include "util.h"
 
-#define DNAME_CNT 512
-#define FNAME_LEN 1024
+enum {
+	DNAME_CNT = 512,
+	FNAME_LEN = 1024
+};
 
 void cleanup();
 
@@ -78,7 +80,7 @@ void size_readable(float *size, const char **unit) {
 	const char *units[] = { "", "K", "M", "G" };
 	int i;
 
-	for (i = 0; i < LEN(units) && *size > 1024; ++i)
+	for (i = 0; i < LEN(units) && *size > 1024; i++)
 		*size /= 1024;
 	*unit = units[MIN(i, LEN(units) - 1)];
 }
diff --git a/window.c b/window.c
index 7508641..4da13d5 100644
--- a/window.c
+++ b/window.c
@@ -16,6 +16,8 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
+#define _WINDOW_CONFIG
+
 #include <string.h>
 
 #include <X11/Xutil.h>