diff --git a/CHANGES b/CHANGES
index 2f84a8c9..e50acaff 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,9 @@
 03 October 2007
 
+* (nicm) Rewrite command handling so commands are much more generic and the
+  same commands are used for command line and keys (although most will probably
+  need to check how they are called). Currently incomplete (only new/detach/ls
+  implemented).
 * (nicm) String number arguments. So you can do: tmux bind ^Q create "blah".
 * (nicm) Key binding. tmux bind key command [argument] and tmux unbind key.
   Key names are in a table in key-string.c, plus A is A, ^A is ctrl-A.
@@ -99,5 +103,5 @@
   (including mutt, emacs). No status bar yet and no key remapping or other
   customisation.
 
-$Id: CHANGES,v 1.25 2007-10-03 12:34:16 nicm Exp $
+$Id: CHANGES,v 1.26 2007-10-03 21:31:06 nicm Exp $
 
diff --git a/Makefile b/Makefile
index 5da175b0..b1d68369 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# $Id: Makefile,v 1.12 2007-10-03 11:26:34 nicm Exp $
+# $Id: Makefile,v 1.13 2007-10-03 21:31:07 nicm Exp $
 
 .SUFFIXES: .c .o .y .h
 .PHONY: clean
@@ -17,9 +17,10 @@ DEBUG=
 META?= \002 # C-b
 
 SRCS= tmux.c server.c server-msg.c server-fn.c buffer.c buffer-poll.c status.c \
-      xmalloc.c xmalloc-debug.c cmd.c input.c input-keys.c screen.c window.c \
-      session.c local.c log.c client.c client-msg.c client-fn.c op.c op-list.c \
-      key-string.c
+      xmalloc.c xmalloc-debug.c input.c input-keys.c screen.c window.c \
+      session.c local.c log.c client.c client-msg.c client-fn.c key-string.c \
+      key-bindings.c \
+      cmd.c cmd-new-session.c cmd-detach-session.c cmd-list-sessions.c 
 
 YACC= yacc -d
 
diff --git a/TODO b/TODO
index 9ee064ee..2d696923 100644
--- a/TODO
+++ b/TODO
@@ -33,8 +33,11 @@
 	IPC is arse-about-face: too much overhead. 8-byte header for each
 	packet... hrm. already scanning output for \e, could add an extra
 	byte to it for message
-- could use bsearch all over the place
+- could use bsearch all over the place or get rid of smaller tables (clientmsg)
 - better errors when creating new windows/sessions (how?)
+- commands should have to care less about CMD_KEY
+- CLIENT_HOLD sucks
+- session with CMD_NOSESSION should be an error
 
 -- For 0.1 --------------------------------------------------------------------
 - man page
diff --git a/client-msg.c b/client-msg.c
index 0a17d67e..6bcd8173 100644
--- a/client-msg.c
+++ b/client-msg.c
@@ -1,4 +1,4 @@
-/* $Id: client-msg.c,v 1.5 2007-10-03 10:18:31 nicm Exp $ */
+/* $Id: client-msg.c,v 1.6 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -39,7 +39,6 @@ struct client_msg client_msg_table[] = {
 	{ MSG_DATA, client_msg_fn_data },
 	{ MSG_DETACH, client_msg_fn_detach },
 	{ MSG_ERROR, client_msg_fn_error },
-	{ MSG_OKAY, client_msg_fn_okay },
 	{ MSG_PAUSE, client_msg_fn_pause },
 };
 #define NCLIENTMSG (sizeof client_msg_table / sizeof client_msg_table[0])
@@ -73,7 +72,6 @@ client_msg_dispatch(struct client_ctx *cctx, char **error)
 	}
 }
 
-/* Data message from server. */
 int
 client_msg_fn_data(
     struct hdr *hdr, struct client_ctx *cctx, unused char **error)
@@ -82,7 +80,6 @@ client_msg_fn_data(
 	return (0);
 }
 
-/* Pause message from server. */
 int
 client_msg_fn_pause(
     struct hdr *hdr, unused struct client_ctx *cctx, unused char **error)
@@ -92,17 +89,6 @@ client_msg_fn_pause(
 	return (1);
 }
 
-/* Okay message from server. */
-int
-client_msg_fn_okay(
-    struct hdr *hdr, unused struct client_ctx *cctx, unused char **error)
-{
-	if (hdr->size != 0)
-		fatalx("bad MSG_OKAY size");
-	return (0);
-}
-
-/* Error message from server. */
 int
 client_msg_fn_error(struct hdr *hdr, struct client_ctx *cctx, char **error)
 {
@@ -116,7 +102,6 @@ client_msg_fn_error(struct hdr *hdr, struct client_ctx *cctx, char **error)
 	return (-1);
 }
 
-/* Detach message from server. */
 int
 client_msg_fn_detach(
     struct hdr *hdr, unused struct client_ctx *cctx, char **error)
diff --git a/client.c b/client.c
index 77a1fb68..0c60d540 100644
--- a/client.c
+++ b/client.c
@@ -1,4 +1,4 @@
-/* $Id: client.c,v 1.10 2007-10-03 10:18:32 nicm Exp $ */
+/* $Id: client.c,v 1.11 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -38,33 +38,19 @@ int	client_process_local(struct client_ctx *, char **);
 int
 client_init(char *path, struct client_ctx *cctx, int start_server)
 {
-	struct sockaddr_un	sa;
-	struct stat		sb;
-	size_t			sz;
-	int			mode;
-	u_int			retries;
+	struct sockaddr_un		sa;
+	struct stat			sb;
+	struct msg_identify_data	data;
+	struct winsize			ws;
+	size_t				sz;
+	int				mode;
+	u_int				retries;
 
 	if (path == NULL) {
 		xasprintf(&path,
 		    "%s/%s-%lu", _PATH_TMP, __progname, (u_long) getuid());
 	}
 
-	if (start_server) {
-		if (!isatty(STDIN_FILENO)) {
-			log_warnx("stdin is not a tty");
-			return (-1);
-		}
-		if (!isatty(STDOUT_FILENO)) {
-			log_warnx("stdout is not a tty");
-			return (-1);
-		}
-
-		if (ioctl(STDIN_FILENO, TIOCGWINSZ, &cctx->ws) == -1) {
-			log_warn("ioctl(TIOCGWINSZ)");
-			return (-1);
-		}
-	}
-
 	retries = 0;
 retry:
 	if (stat(path, &sb) != 0) {
@@ -121,50 +107,18 @@ retry:
 	cctx->srv_in = buffer_create(BUFSIZ);
 	cctx->srv_out = buffer_create(BUFSIZ);
 
-	return (0);
-}
-
-int
-client_flush(struct client_ctx *cctx)
-{
-	struct pollfd	 pfd;
-	struct hdr	 hdr;
-
-	for (;;) {
-		pfd.fd = cctx->srv_fd;
-		pfd.events = POLLIN;
-		if (BUFFER_USED(cctx->srv_out) > 0)
-			pfd.events |= POLLOUT;
-	
-		if (poll(&pfd, 1, INFTIM) == -1) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			fatal("poll failed");
+	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
+		if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
+			log_warn("ioctl(TIOCGWINSZ)");
+			return (-1);
 		}
 
-		if (buffer_poll(&pfd, cctx->srv_in, cctx->srv_out) != 0) {
-			log_warnx("lost server");
-			return (1);
-		}
-
-		if (BUFFER_USED(cctx->srv_in) < sizeof hdr)
-			continue;
-		memcpy(&hdr, BUFFER_OUT(cctx->srv_in), sizeof hdr);
-		if (BUFFER_USED(cctx->srv_in) < (sizeof hdr) + hdr.size)
-			continue;
-		buffer_remove(cctx->srv_in, sizeof hdr);
-
-		if (hdr.type == MSG_OKAY)
-			return (0);
-		if (hdr.type == MSG_ERROR) {
-			if (hdr.size > INT_MAX - 1)
-				fatalx("bad MSG_ERROR size");
-			log_warnx(
-			    "%.*s", (int) hdr.size, BUFFER_OUT(cctx->srv_in));
-			return (1);
-		}
-		fatalx("unexpected message");
+		data.sx = ws.ws_col;
+		data.sy = ws.ws_row;
+		client_write_server(cctx, MSG_IDENTIFY, &data, sizeof data);
 	}
+
+	return (0);
 }
 
 int
@@ -244,14 +198,15 @@ local_dead:
 void
 client_handle_winch(struct client_ctx *cctx)
 {
-	struct size_data	data;
-
-	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &cctx->ws) == -1)
+	struct msg_resize_data	data;
+	struct winsize		ws;
+	
+	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
 		fatal("ioctl failed");
 
-	data.sx = cctx->ws.ws_col;
-	data.sy = cctx->ws.ws_row;
-	client_write_server(cctx, MSG_SIZE, &data, sizeof data);
+	data.sx = ws.ws_col;
+	data.sy = ws.ws_row;
+	client_write_server(cctx, MSG_RESIZE, &data, sizeof data);
 	
 	sigwinch = 0;
 }
diff --git a/cmd-detach-session.c b/cmd-detach-session.c
new file mode 100644
index 00000000..bcb858a7
--- /dev/null
+++ b/cmd-detach-session.c
@@ -0,0 +1,137 @@
+/* $Id: cmd-detach-session.c,v 1.1 2007-10-03 21:31:07 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * 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 MIND, 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 <sys/types.h>
+
+#include <getopt.h>
+
+#include "tmux.h"
+
+/*
+ * Detach session. If called with -a detach all clients attached to specified
+ * session, otherwise detach current session on key press only.
+ */
+
+int		 cmd_detach_session_parse(void **, int, char **, char **);
+const char	*cmd_detach_session_usage(void);
+void		 cmd_detach_session_exec(void *, struct cmd_ctx *);
+void		 cmd_detach_session_send(void *, struct buffer *);
+void		 cmd_detach_session_recv(void **, struct buffer *);
+void		 cmd_detach_session_free(void *);
+
+struct cmd_detach_session_data {
+	int	 flag_all;
+};
+
+const struct cmd_entry cmd_detach_session_entry = {
+	CMD_DETACHSESSION, "detach-session", "detach", 0,
+	cmd_detach_session_parse,
+	cmd_detach_session_usage,
+	cmd_detach_session_exec,
+	cmd_detach_session_send,
+	cmd_detach_session_recv,
+	cmd_detach_session_free
+};
+
+int
+cmd_detach_session_parse(void **ptr, int argc, char **argv, char **cause)
+{
+	struct cmd_detach_session_data	*data;
+	int				 opt;
+
+	*ptr = data = xmalloc(sizeof *data);
+	data->flag_all = 0;
+
+	while ((opt = getopt(argc, argv, "a")) != EOF) {
+		switch (opt) {
+		case 'a':
+			data->flag_all = 1;
+			break;
+		default:
+			goto usage;
+		}
+	}	
+	argc -= optind;
+	argv -= optind;
+	if (argc != 0)
+		goto usage;
+
+	return (0);
+
+usage:
+	usage(cause, "%s", cmd_detach_session_usage());
+
+	xfree(data);
+	return (-1);
+}
+
+const char *
+cmd_detach_session_usage(void)
+{
+	return ("detach-session [-a]");
+}
+
+void
+cmd_detach_session_exec(void *ptr, struct cmd_ctx *ctx)
+{
+	struct cmd_detach_session_data	*data = ptr, std = { 0 };
+	struct client			*c = ctx->client, *cp;
+	struct session			*s = ctx->session;
+	u_int				 i;
+
+	if (data == NULL)
+		data = &std;
+
+	if (data->flag_all) {
+		for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
+			cp = ARRAY_ITEM(&clients, i);
+			if (cp == NULL || cp->session != s)
+				continue;
+			server_write_client(cp, MSG_DETACH, NULL, 0);
+		}
+	} else if (ctx->flags & CMD_KEY)
+		server_write_client(c, MSG_DETACH, NULL, 0);
+
+	if (!(ctx->flags & CMD_KEY))
+		server_write_client(c, MSG_EXIT, NULL, 0);
+}
+
+void
+cmd_detach_session_send(void *ptr, struct buffer *b)
+{
+	struct cmd_detach_session_data	*data = ptr;
+
+	buffer_write(b, data, sizeof *data);
+}
+
+void
+cmd_detach_session_recv(void **ptr, struct buffer *b)
+{
+	struct cmd_detach_session_data	*data;
+
+	*ptr = data = xmalloc(sizeof *data);
+	buffer_read(b, data, sizeof *data);
+}
+
+void
+cmd_detach_session_free(void *ptr)
+{
+	struct cmd_detach_session_data	*data = ptr;
+
+	xfree(data);
+}
diff --git a/cmd-list-sessions.c b/cmd-list-sessions.c
new file mode 100644
index 00000000..47f3f0a0
--- /dev/null
+++ b/cmd-list-sessions.c
@@ -0,0 +1,70 @@
+/* $Id: cmd-list-sessions.c,v 1.1 2007-10-03 21:31:07 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * 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 MIND, 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 <sys/types.h>
+
+#include <getopt.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * List all sessions.
+ */
+
+int		 cmd_list_sessions_parse(void **, int, char **, char **);
+const char	*cmd_list_sessions_usage(void);
+void		 cmd_list_sessions_exec(void *, struct cmd_ctx *);
+
+const struct cmd_entry cmd_list_sessions_entry = {
+	CMD_LISTSESSIONS, "list-sessions", "ls", CMD_NOSESSION,
+	NULL,
+	NULL,
+	cmd_list_sessions_exec,
+	NULL,
+	NULL,
+	NULL
+};
+
+void
+cmd_list_sessions_exec(unused void *ptr, struct cmd_ctx *ctx)
+{
+	struct client	*c = ctx->client;
+	struct session	*s = ctx->session;
+	char		*tim;
+	u_int		 i, j, n;
+
+	for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
+		s = ARRAY_ITEM(&sessions, i);
+		if (s == NULL)
+			continue;
+
+		n = 0;
+		for (j = 0; j < ARRAY_LENGTH(&s->windows); j++) {
+			if (ARRAY_ITEM(&s->windows, j) != NULL)
+				n++;
+		}
+		tim = ctime(&s->tim);
+		*strchr(tim, '\n') = '\0';
+
+		ctx->print(ctx, "%s: %u windows (created %s)", s->name, n, tim);
+	}
+
+	if (!(ctx->flags & CMD_KEY))
+		server_write_client(c, MSG_EXIT, NULL, 0);
+}
diff --git a/cmd-new-session.c b/cmd-new-session.c
new file mode 100644
index 00000000..6ed04ed8
--- /dev/null
+++ b/cmd-new-session.c
@@ -0,0 +1,162 @@
+/* $Id: cmd-new-session.c,v 1.1 2007-10-03 21:31:07 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * 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 MIND, 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 <sys/types.h>
+
+#include <getopt.h>
+
+#include "tmux.h"
+
+/*
+ * Create a new session and attach to the current terminal unless -d is given.
+ */
+
+int		 cmd_new_session_parse(void **, int, char **, char **);
+const char	*cmd_new_session_usage(void);
+void		 cmd_new_session_exec(void *, struct cmd_ctx *);
+void		 cmd_new_session_send(void *, struct buffer *);
+void		 cmd_new_session_recv(void **, struct buffer *);
+void		 cmd_new_session_free(void *);
+
+struct cmd_new_session_data {
+	char	*name;
+	int	 flag_detached;
+};
+
+const struct cmd_entry cmd_new_session_entry = {
+	CMD_NEWSESSION, "new-session", "new", CMD_STARTSERVER|CMD_NOSESSION,
+	cmd_new_session_parse,
+	cmd_new_session_usage,
+	cmd_new_session_exec, 
+	cmd_new_session_send,
+	cmd_new_session_recv,
+	cmd_new_session_free
+};
+
+int
+cmd_new_session_parse(void **ptr, int argc, char **argv, char **cause)
+{
+	struct cmd_new_session_data	*data;
+	int				 opt;
+
+	*ptr = data = xmalloc(sizeof *data);
+	data->flag_detached = 0;
+	data->name = NULL;
+
+	while ((opt = getopt(argc, argv, "ds:")) != EOF) {
+		switch (opt) {
+		case 'd':
+			data->flag_detached = 1;
+			break;
+		case 's':
+			data->name = xstrdup(optarg);
+			break;
+		default:
+			goto usage;
+		}
+	}	
+	argc -= optind;
+	argv -= optind;
+	if (argc != 0)
+		goto usage;
+
+	return (0);
+
+usage:
+	usage(cause, "%s", cmd_new_session_usage());
+
+	if (data->name != NULL)
+		xfree(data->name);
+	xfree(data);
+	return (-1);
+}
+
+const char *
+cmd_new_session_usage(void)
+{
+	return ("new-session [-d] [-n session name]");
+}
+
+void
+cmd_new_session_exec(void *ptr, struct cmd_ctx *ctx)
+{
+	struct cmd_new_session_data	*data = ptr, std = { NULL, 0 };
+	struct client			*c = ctx->client;
+	u_int				 sy;
+	
+	if (data == NULL)
+		data = &std;
+
+	if (ctx->flags & CMD_KEY)
+		return;
+
+	if (!data->flag_detached && !(c->flags & CLIENT_TERMINAL)) {
+		ctx->error(ctx, "not a terminal");
+		return;
+	}
+
+	if (data->name != NULL && session_find(data->name) != NULL) {
+		ctx->error(ctx, "duplicate session: %s", data->name);
+		return;
+	}
+
+	sy = c->sy;
+	if (sy < status_lines)
+		sy = status_lines + 1;
+	sy -= status_lines;
+	
+	c->session = session_create(data->name, default_command, c->sx, sy);
+	if (c->session == NULL)
+		fatalx("session_create failed");
+
+	if (data->flag_detached)
+		server_write_client(c, MSG_EXIT, NULL, 0);
+	else {
+		server_write_client(c, MSG_READY, NULL, 0);
+		server_draw_client(c);
+	}
+}
+
+void
+cmd_new_session_send(void *ptr, struct buffer *b)
+{
+	struct cmd_new_session_data	*data = ptr;
+
+	buffer_write(b, data, sizeof *data);
+	cmd_send_string(b, data->name);
+}
+
+void
+cmd_new_session_recv(void **ptr, struct buffer *b)
+{
+	struct cmd_new_session_data	*data;
+
+	*ptr = data = xmalloc(sizeof *data);
+	buffer_read(b, data, sizeof *data);
+	data->name = cmd_recv_string(b);
+}
+
+void
+cmd_new_session_free(void *ptr)
+{
+	struct cmd_new_session_data	*data = ptr;
+
+	if (data->name != NULL)
+		xfree(data->name);
+	xfree(data);
+}
diff --git a/cmd.c b/cmd.c
index c5823f3b..cf171de4 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,4 +1,4 @@
-/* $Id: cmd.c,v 1.4 2007-10-03 12:43:47 nicm Exp $ */
+/* $Id: cmd.c,v 1.5 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -18,243 +18,163 @@
 
 #include <sys/types.h>
 
-#include <stdlib.h>
+#include <getopt.h>
 #include <string.h>
 
 #include "tmux.h"
 
-int	cmd_prefix = META;
-
-void	cmd_fn_detach(struct client *, struct cmd *);
-void	cmd_fn_last(struct client *, struct cmd *);
-void	cmd_fn_meta(struct client *, struct cmd *);
-void	cmd_fn_new(struct client *, struct cmd *);
-void	cmd_fn_next(struct client *, struct cmd *);
-void	cmd_fn_previous(struct client *, struct cmd *);
-void	cmd_fn_refresh(struct client *, struct cmd *);
-void	cmd_fn_select(struct client *, struct cmd *);
-void	cmd_fn_info(struct client *, struct cmd *);
-
-const struct cmd cmd_default[] = {
-	{ '0', cmd_fn_select, 0, NULL },
-	{ '1', cmd_fn_select, 1, NULL },
-	{ '2', cmd_fn_select, 2, NULL },
-	{ '3', cmd_fn_select, 3, NULL },
-	{ '4', cmd_fn_select, 4, NULL },
-	{ '5', cmd_fn_select, 5, NULL },
-	{ '6', cmd_fn_select, 6, NULL },
-	{ '7', cmd_fn_select, 7, NULL },
-	{ '8', cmd_fn_select, 8, NULL },
-	{ '9', cmd_fn_select, 9, NULL },
-	{ 'C', cmd_fn_new, 0, NULL },
-	{ 'c', cmd_fn_new, 0, NULL },
-	{ 'D', cmd_fn_detach, 0, NULL },
-	{ 'd', cmd_fn_detach, 0, NULL },
-	{ 'N', cmd_fn_next, 0, NULL },
-	{ 'n', cmd_fn_next, 0, NULL },
-	{ 'P', cmd_fn_previous, 0, NULL },
-	{ 'p', cmd_fn_previous, 0, NULL },
-	{ 'R', cmd_fn_refresh, 0, NULL },
-	{ 'r', cmd_fn_refresh, 0, NULL },
-	{ 'L', cmd_fn_last, 0, NULL },
-	{ 'l', cmd_fn_last, 0, NULL },
-	{ 'I', cmd_fn_info, 0, NULL },
-	{ 'i', cmd_fn_info, 0, NULL },
-	{ META, cmd_fn_meta, 0, NULL },
+const struct cmd_entry *cmd_table[] = {
+	&cmd_detach_session_entry,
+	&cmd_list_sessions_entry,
+	&cmd_new_session_entry,
+	NULL
 };
-u_int	cmd_count = (sizeof cmd_default / sizeof cmd_default[0]);
-struct cmd *cmd_table;
 
-const struct bind cmd_bind_table[] = {
-	{ "detach", 	cmd_fn_detach, 0 },
-	{ "info",	cmd_fn_info, 0 },
-	{ "last",	cmd_fn_last, 0 },
-	{ "meta",	cmd_fn_meta, 0 },
-	{ "new", 	cmd_fn_new, BIND_STRING|BIND_USER },
-	{ "next",	cmd_fn_next, 0 },
-	{ "previous", 	cmd_fn_previous, 0 },
-	{ "refresh",	cmd_fn_refresh, 0 },
-	{ "select", 	cmd_fn_select, BIND_NUMBER|BIND_USER },
-};
-#define NCMDBIND (sizeof cmd_bind_table / sizeof cmd_bind_table[0])
-
-const struct bind *
-cmd_lookup_bind(const char *name)
+struct cmd *
+cmd_parse(int argc, char **argv, char **cause)
 {
-	const struct bind	*bind;
-	u_int		         i;
+	const struct cmd_entry **this, *entry;
+	struct cmd	        *cmd;
+	int			 opt;
 
-	for (i = 0; i < NCMDBIND; i++) {
-		bind = cmd_bind_table + i;
-		if (strcmp(bind->name, name) == 0)
-			return (bind);
+	*cause = NULL;
+	if (argc == 0)
+		return (NULL);
+
+	entry = NULL;
+	for (this = cmd_table; *this != NULL; this++) {
+		if (strcmp((*this)->alias, argv[0]) == 0) {
+			entry = *this;
+			break;
+		}
+
+		if (strncmp((*this)->name, argv[0], strlen(argv[0])) != 0)
+			continue;
+		if (entry != NULL) {
+			xasprintf(cause, "ambiguous command: %s", argv[0]);
+			return (NULL);
+		}
+		entry = *this;
 	}
+	if (entry == NULL) {
+		xasprintf(cause, "unknown command: %s", argv[0]);
+		return (NULL);
+	}
+
+	optind = 1;
+	if (entry->parse == NULL) {
+		while ((opt = getopt(argc, argv, "")) != EOF) {
+			switch (opt) {
+			default:
+				goto usage;
+			}
+		}
+		argc -= optind;
+		argv += optind;
+		if (argc != 0)
+			goto usage;
+	}
+
+	cmd = xmalloc(sizeof *cmd);
+	cmd->entry = entry;
+	if (entry->parse != NULL) {
+		if (entry->parse(&cmd->data, argc, argv, cause) != 0) {
+			xfree(cmd);
+			return (NULL);
+		}
+	}
+	return (cmd);
+
+usage:
+	if (entry->usage == NULL)
+		usage(cause, "%s", entry->name);
+	else
+		usage(cause, "%s", entry->usage());
 	return (NULL);
 }
 
 void
-cmd_add_bind(int key, u_int num, char *str, const struct bind *bind)
+cmd_exec(struct cmd *cmd, struct cmd_ctx *ctx)
 {
-	struct cmd	*cmd = NULL;
-	u_int		 i;
-
-	for (i = 0; i < cmd_count; i++) {
-		cmd = cmd_table + i;
-		if (cmd->key == key)
-			break;
-	}
-	if (i == cmd_count) {
-		for (i = 0; i < cmd_count; i++) {
-			cmd = cmd_table + i;
-			if (cmd->key == KEYC_NONE)
-				break;
-		}
-		if (i == cmd_count) {
-			cmd_count++;
-			cmd_table = xrealloc(cmd_table,
-			    cmd_count, sizeof cmd_table[0]);
-			cmd = cmd_table + cmd_count - 1;
-		}
-	}
-
-	cmd->key = key;
-	cmd->fn = bind->fn;
-	if (bind->flags & BIND_USER) {
-		if (bind->flags & BIND_STRING)
-			cmd->str = xstrdup(str);
-		if (bind->flags & BIND_NUMBER)
-			cmd->num = num;
-	}
+	return (cmd->entry->exec(cmd->data, ctx));
 }
 
 void
-cmd_remove_bind(int key)
+cmd_send(struct cmd *cmd, struct buffer *b)
 {
-	struct cmd	*cmd;
-	u_int		 i;
+	buffer_write(b, &cmd->entry->type, sizeof cmd->entry->type);
 
-	for (i = 0; i < cmd_count; i++) {
-		cmd = cmd_table + i;
-		if (cmd->key == key) {
-			cmd->key = KEYC_NONE;
+	if (cmd->entry->send == NULL)
+		return;
+	return (cmd->entry->send(cmd->data, b));
+}
+
+struct cmd *
+cmd_recv(struct buffer *b)
+{
+	const struct cmd_entry **this, *entry;
+	struct cmd   	        *cmd;
+	enum cmd_type		 type;
+
+	buffer_read(b, &type, sizeof type);
+	
+	entry = NULL;
+	for (this = cmd_table; *this != NULL; this++) {
+		if ((*this)->type == type) {
+			entry = *this;
 			break;
 		}
 	}
+	if (*this == NULL)
+		return (NULL);
+
+	cmd = xmalloc(sizeof *cmd);
+	cmd->entry = entry;
+
+	if (cmd->entry->recv != NULL)
+		cmd->entry->recv(&cmd->data, b);
+	return (cmd);
 }
 
 void
-cmd_init(void)
+cmd_free(struct cmd *cmd)
 {
-	cmd_table = xmalloc(sizeof cmd_default);
-	memcpy(cmd_table, cmd_default, sizeof cmd_default);
+	if (cmd->entry->free != NULL)
+		cmd->entry->free(cmd->data);
+	xfree(cmd);
 }
 
 void
-cmd_free(void)
+cmd_send_string(struct buffer *b, const char *s)
 {
-	/* XXX free strings */
-	xfree(cmd_table);
-}
-
-void
-cmd_dispatch(struct client *c, int key)
-{
-	struct cmd	*cmd;
-	u_int		 i;
-
-	for (i = 0; i < cmd_count; i++) {
-		cmd = cmd_table + i;
-		if (cmd->key != KEYC_NONE && cmd->key == key)
-			cmd->fn(c, cmd);
+	size_t	n;
+	
+	if (s == NULL) {
+		n = 0;
+		buffer_write(b, &n, sizeof n);
+		return;
 	}
+
+	n = strlen(s) + 1;
+	buffer_write(b, &n, sizeof n);
+
+	buffer_write(b, s, n);
 }
 
-void
-cmd_fn_new(struct client *c, struct cmd *cmd)
+char *
+cmd_recv_string(struct buffer *b)
 {
-	char	*s;
+	char   *s;
+	size_t	n;
 
-	s = cmd->str;
-	if (s == NULL)
-		s = default_command;
-	if (session_new(c->session, s, c->sx, c->sy) != 0)
-		server_write_message(c, "%s failed", s); /* XXX */
-	else
-		server_draw_client(c, 0, c->sy - 1);
-}
-
-void
-cmd_fn_detach(struct client *c, unused struct cmd *cmd)
-{
-	server_write_client(c, MSG_DETACH, NULL, 0);
-}
-
-void
-cmd_fn_last(struct client *c, unused struct cmd *cmd)
-{
-	if (session_last(c->session) == 0)
-		server_window_changed(c);
-	else
-		server_write_message(c, "No last window"); 
-}
-
-void
-cmd_fn_meta(struct client *c, unused struct cmd *cmd)
-{
-	window_key(c->session->window, cmd_prefix);
-}
-
-void
-cmd_fn_next(struct client *c, unused struct cmd *cmd)
-{
-	if (session_next(c->session) == 0)
-		server_window_changed(c);
-	else
-		server_write_message(c, "No next window"); 
-}
-
-void
-cmd_fn_previous(struct client *c, unused struct cmd *cmd)
-{
-	if (session_previous(c->session) == 0)
-		server_window_changed(c);
-	else
-		server_write_message(c, "No previous window"); 
-}
-
-void
-cmd_fn_refresh(struct client *c, unused struct cmd *cmd)
-{
-	server_draw_client(c, 0, c->sy - 1);
-}
-
-void
-cmd_fn_select(struct client *c, struct cmd *cmd)
-{
-	if (session_select(c->session, cmd->num) == 0)
-		server_window_changed(c);
-	else
-		server_write_message(c, "Window %u not present", cmd->num);
-}
-
-void
-cmd_fn_info(struct client *c, unused struct cmd *cmd)
-{
-	struct window	*w;
-	char 		*buf;
-	size_t		 len;
-	u_int		 i;
-
-	len = c->sx + 1;
-	buf = xmalloc(len);
-
-	w = c->session->window;
-	window_index(&c->session->windows, w, &i);
-	xsnprintf(buf, len, "%u:%s \"%s\" (size %u,%u) (cursor %u,%u) "
-	    "(region %u,%u)", i, w->name, w->screen.title, w->screen.sx,
-	    w->screen.sy, w->screen.cx, w->screen.cy, w->screen.ry_upper,
-	    w->screen.ry_lower);
-
-	server_write_message(c, "%s", buf);
-	xfree(buf);
+	buffer_read(b, &n, sizeof n);
+
+	if (n == 0)
+		return (NULL);
+	
+	s = xmalloc(n);
+	buffer_read(b, s, n);
+	s[n - 1] = '\0';
+
+	return (s);
 }
diff --git a/key-bindings.c b/key-bindings.c
new file mode 100644
index 00000000..7cbd556b
--- /dev/null
+++ b/key-bindings.c
@@ -0,0 +1,214 @@
+/* $Id: key-bindings.c,v 1.1 2007-10-03 21:31:07 nicm Exp $ */
+
+/*
+ * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
+ *
+ * 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 MIND, 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+ARRAY_DECL(, struct binding *) key_bindings;
+
+void	key_bindings_error(struct cmd_ctx *, const char *, ...);
+void	key_bindings_print(struct cmd_ctx *, const char *, ...);
+
+void
+key_bindings_add(int key, struct cmd *cmd)
+{
+	struct binding	*bd;
+	u_int		 i;
+
+	bd = NULL;
+	for (i = 0; i < ARRAY_LENGTH(&key_bindings); i++) {
+		bd = ARRAY_ITEM(&key_bindings, i);
+		if (bd->key == key)
+			break;
+	}
+	if (i == ARRAY_LENGTH(&key_bindings)) {
+		bd = xmalloc(sizeof *bd);
+		ARRAY_ADD(&key_bindings, bd);
+	}
+
+	bd->key = key;
+	bd->cmd = cmd;
+}
+
+void
+key_bindings_remove(int key)
+{
+	struct binding	*bd;
+	u_int		 i;
+
+	bd = NULL;
+	for (i = 0; i < ARRAY_LENGTH(&key_bindings); i++) {
+		bd = ARRAY_ITEM(&key_bindings, i);
+		if (bd->key == key)
+			break;
+	}
+	if (i == ARRAY_LENGTH(&key_bindings))
+		return;
+
+	cmd_free(bd->cmd);
+	xfree(bd);
+}
+
+void
+key_bindings_init(void)
+{
+	struct {
+		int			 key;
+		const struct cmd_entry	*entry;
+	} table[] = {
+		{ 'D', &cmd_detach_session_entry },
+		{ 'd', &cmd_detach_session_entry },
+		{ 'S', &cmd_list_sessions_entry },
+		{ 's', &cmd_list_sessions_entry },
+/*		{ 'C', &cmd_new_window },
+		{ 'c', &cmd_new_window },
+		{ 'N', &cmd_next_window },
+		{ 'n', &cmd_next_window },
+		{ 'P', &cmd_previous_window },
+		{ 'p', &cmd_previous_window },
+		{ 'R', &cmd_refresh_client },
+		{ 'r', &cmd_refresh_client },
+		{ 'L', &cmd_last_window },
+		{ 'l', &cmd_last_window },
+		{ 'I', &cmd_windo_info },
+		{ 'i', &cmd_window_info },
+		{ META, &cmd_meta_entry },
+*//*	{ '0', cmdx_fn_select, 0, NULL },
+	{ '1', cmdx_fn_select, 1, NULL },
+	{ '2', cmdx_fn_select, 2, NULL },
+	{ '3', cmdx_fn_select, 3, NULL },
+	{ '4', cmdx_fn_select, 4, NULL },
+	{ '5', cmdx_fn_select, 5, NULL },
+	{ '6', cmdx_fn_select, 6, NULL },
+	{ '7', cmdx_fn_select, 7, NULL },
+	{ '8', cmdx_fn_select, 8, NULL },
+	{ '9', cmdx_fn_select, 9, NULL },
+*/
+	};
+	u_int		 i;
+	struct cmd	*cmd;
+
+	for (i = 0; i < (sizeof table / sizeof table[0]); i++) {
+		cmd = xmalloc(sizeof *cmd);
+		cmd->entry = table[i].entry;
+		cmd->data = NULL;
+		key_bindings_add(table[i].key, cmd);
+	}
+}
+
+void
+key_bindings_free(void)
+{
+	struct binding	*bd;
+	u_int		 i;
+
+	for (i = 0; i < ARRAY_LENGTH(&key_bindings); i++) {
+		bd = ARRAY_ITEM(&key_bindings, i);
+
+		cmd_free(bd->cmd);
+		xfree(bd);
+	}
+	
+	ARRAY_FREEALL(&key_bindings);
+}
+
+void
+key_bindings_error(struct cmd_ctx *ctx, const char *fmt, ...)
+{
+	va_list	ap;
+	char   *msg;
+
+	va_start(ap, fmt);
+	xvasprintf(&msg, fmt, ap);
+	va_end(ap);
+
+	server_write_message(ctx->client, msg);
+	xfree(msg);
+}
+
+void
+key_bindings_print(struct cmd_ctx *ctx, const char *fmt, ...)
+{
+	struct client	*c = ctx->client;
+	struct hdr	 hdr;
+	va_list		 ap;
+	char		*msg;
+	size_t		 size;
+	u_int		 i;
+
+	buffer_ensure(c->out, sizeof hdr);
+	buffer_add(c->out, sizeof hdr);
+	size = BUFFER_USED(c->out);
+
+	if (!(c->flags & CLIENT_HOLD)) {
+		input_store_zero(c->out, CODE_CURSOROFF);
+		for (i = 0; i < c->session->window->screen.sy; i++) {
+			input_store_two(c->out, CODE_CURSORMOVE, i + 1, 1);
+			input_store_zero(c->out, CODE_CLEARLINE);
+		}			
+		input_store_two(c->out, CODE_CURSORMOVE, 1, 1);
+		input_store_two(c->out, CODE_ATTRIBUTES, 0, 0x88);
+		
+		c->flags |= CLIENT_HOLD;
+	}
+
+	va_start(ap, fmt);
+	xvasprintf(&msg, fmt, ap);
+	va_end(ap);
+
+	buffer_write(c->out, msg, strlen(msg));
+	input_store8(c->out, '\r');
+	input_store8(c->out, '\n');
+	xfree(msg);
+
+	size = BUFFER_USED(c->out) - size;
+	hdr.type = MSG_DATA;
+	hdr.size = size;
+	memcpy(BUFFER_IN(c->out) - size - sizeof hdr, &hdr, sizeof hdr);
+}
+
+void
+key_bindings_dispatch(int key, struct client *c)
+{
+	struct cmd_ctx	 ctx;
+	struct binding	*bd;
+	u_int		 i;
+
+	bd = NULL;
+	for (i = 0; i < ARRAY_LENGTH(&key_bindings); i++) {
+		bd = ARRAY_ITEM(&key_bindings, i);
+		if (bd->key == key)
+			break;
+	}
+	if (i == ARRAY_LENGTH(&key_bindings))
+		return;
+
+	ctx.session = c->session;
+
+	ctx.error = key_bindings_error;
+	ctx.print = key_bindings_print;
+
+	ctx.client = c;
+	ctx.flags = CMD_KEY;
+
+	cmd_exec(bd->cmd, &ctx);
+}
diff --git a/op-list.c b/op-list.c
deleted file mode 100644
index a8405d7c..00000000
--- a/op-list.c
+++ /dev/null
@@ -1,199 +0,0 @@
-/* $Id: op-list.c,v 1.6 2007-09-29 15:06:00 nicm Exp $ */
-
-/*
- * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
- *
- * 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 MIND, 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 <sys/types.h>
-
-#include <errno.h>
-#include <getopt.h>
-#include <string.h>
-
-#include "tmux.h"
-
-int
-op_list_sessions(char *path, int argc, unused char **argv)
-{
-	struct client_ctx	cctx;
-	char		       *tim;
-	struct sessions_data    data;
-	struct sessions_entry	ent;
-	struct pollfd		pfd;
-	struct hdr		hdr;
-
-	if (argc != 1)
-		return (usage("list-sessions"));
-
-	if (client_init(path, &cctx, 0) != 0)
-		return (1);
-	client_write_server(&cctx, MSG_SESSIONS, &data, sizeof data);
-
-	for (;;) {
-		pfd.fd = cctx.srv_fd;
-		pfd.events = POLLIN;
-		if (BUFFER_USED(cctx.srv_out) > 0)
-			pfd.events |= POLLOUT;
-
-		if (poll(&pfd, 1, INFTIM) == -1) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			log_warn("poll");
-			return (-1);
-		}
-
-		if (buffer_poll(&pfd, cctx.srv_in, cctx.srv_out) != 0) {
-			log_warnx("lost server");
-			return (-1);
-		}
-
-		if (BUFFER_USED(cctx.srv_in) < sizeof hdr)
-			continue;
-		memcpy(&hdr, BUFFER_OUT(cctx.srv_in), sizeof hdr);
-		if (BUFFER_USED(cctx.srv_in) < (sizeof hdr) + hdr.size)
-			continue;
-		buffer_remove(cctx.srv_in, sizeof hdr);
-
-		if (hdr.type == MSG_ERROR) {
-			if (hdr.size > INT_MAX - 1)
-				fatalx("bad MSG_ERROR size");
-			log_warnx(
-			    "%.*s", (int) hdr.size, BUFFER_OUT(cctx.srv_in));
-			return (1);
-		}		
-		if (hdr.type != MSG_SESSIONS)
-			fatalx("unexpected message");
-
-		if (hdr.size < sizeof data)
-			fatalx("bad MSG_SESSIONS size");
-		buffer_read(cctx.srv_in, &data, sizeof data);
-		hdr.size -= sizeof data; 
-		if (data.sessions == 0 && hdr.size == 0)
-			return (0);
-		if (hdr.size < data.sessions * sizeof ent)
-			fatalx("bad MSG_SESSIONS size");
-
-		while (data.sessions-- > 0) {
-			buffer_read(cctx.srv_in, &ent, sizeof ent);
-
-			tim = ctime(&ent.tim);
-			*strchr(tim, '\n') = '\0';
-
-			printf("%s: %u windows "
-			    "(created %s)\n", ent.name, ent.windows, tim);
-		}
-
-		return (0);
-	}
-}
-
-int
-op_list_windows(char *path, int argc, char **argv)
-{
-	struct client_ctx	cctx;
-	char			name[MAXNAMELEN];
-	int			opt;
-	struct windows_data	data;
-	struct windows_entry	ent;
-	struct pollfd		pfd;
-	struct hdr		hdr;
-
-	*name = '\0';
-	optind = 1;
-	while ((opt = getopt(argc, argv, "s:?")) != EOF) {
-		switch (opt) {
-		case 's':
-			if (strlcpy(name, optarg, sizeof name) >= sizeof name) {
-				log_warnx("%s: session name too long", optarg);
-				return (1);
-			}
-			break;
-		case '?':
-		default:
-			return (usage("list-windows [-s session]"));
-		}
-	}	
-	argc -= optind;
-	argv += optind;			
-	if (argc != 0)
-		return (usage("list-windows [-s session]"));
-
-	if (client_init(path, &cctx, 0) != 0)
-		return (1);
- 
-	client_fill_sessid(&data.sid, name);
-	client_write_server(&cctx, MSG_WINDOWS, &data, sizeof data);
-
-	for (;;) {
-		pfd.fd = cctx.srv_fd;
-		pfd.events = POLLIN;
-		if (BUFFER_USED(cctx.srv_out) > 0)
-			pfd.events |= POLLOUT;
-
-		if (poll(&pfd, 1, INFTIM) == -1) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			log_warn("poll");
-			return (-1);
-		}
-
-		if (buffer_poll(&pfd, cctx.srv_in, cctx.srv_out) != 0) {
-			log_warnx("lost server");
-			return (-1);
-		}
-
-		if (BUFFER_USED(cctx.srv_in) < sizeof hdr)
-			continue;
-		memcpy(&hdr, BUFFER_OUT(cctx.srv_in), sizeof hdr);
-		if (BUFFER_USED(cctx.srv_in) < (sizeof hdr) + hdr.size)
-			continue;
-		buffer_remove(cctx.srv_in, sizeof hdr);
-		
-		if (hdr.type == MSG_ERROR) {
-			if (hdr.size > INT_MAX - 1)
-				fatalx("bad MSG_ERROR size");
-			log_warnx(
-			    "%.*s", (int) hdr.size, BUFFER_OUT(cctx.srv_in));
-			return (1);
-		}		
-		if (hdr.type != MSG_WINDOWS)
-			fatalx("unexpected message");
-
-		if (hdr.size < sizeof data)
-			fatalx("bad MSG_WINDOWS size");
-		buffer_read(cctx.srv_in, &data, sizeof data);
-		hdr.size -= sizeof data; 
-		if (data.windows == 0 && hdr.size == 0) {
-			log_warnx("session not found: %s", name);
-			return (1);
-		}
-		if (hdr.size < data.windows * sizeof ent)
-			fatalx("bad MSG_WINDOWS size");
-
-		while (data.windows-- > 0) {
-			buffer_read(cctx.srv_in, &ent, sizeof ent);
-
-			if (*ent.title != '\0') {
-				printf("%u: %s \"%s\" (%s)\n", ent.idx,
-				    ent.name, ent.title, ent.tty);
-			} else {
-				printf("%u: %s (%s)\n",
-				    ent.idx, ent.name, ent.tty);
-			}
-		}
-
-		return (0);
-	}
-}
diff --git a/op.c b/op.c
deleted file mode 100644
index 80ab66a6..00000000
--- a/op.c
+++ /dev/null
@@ -1,271 +0,0 @@
-/* $Id: op.c,v 1.12 2007-10-03 12:43:47 nicm Exp $ */
-
-/*
- * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
- *
- * 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 MIND, 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 <sys/types.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "tmux.h"
-
-int
-op_new_session(char *path, int argc, char **argv)
-{
-	struct new_data	 	data;	
-	struct client_ctx	cctx;
-	char			name[MAXNAMELEN];
-	int			opt, detached;
-
-	*name = '\0';
-	detached = 0;
-	optind = 1;
-	while ((opt = getopt(argc, argv, "s:d?")) != EOF) {
-		switch (opt) {
-		case 's':
-			if (strlcpy(name, optarg, sizeof name) >= sizeof name) {
-				log_warnx("session name too long: %s", optarg);
-				return (1);
-			}
-			break;
-		case 'd':
-			detached = 1;
-			break;
-		case '?':
-		default:
-			return (usage("new-session [-d] [-s session]"));
-		}
-	}	
-	argc -= optind;
-	argv += optind;			
-	if (argc != 0)
-		return (usage("new-session [-s session]"));
-
-	if (client_init(path, &cctx, 1) != 0)
-		return (1);
-
-	strlcpy(data.name, name, sizeof data.name);
-	data.sx = cctx.ws.ws_col;
-	data.sy = cctx.ws.ws_row;
-	client_write_server(&cctx, MSG_NEW, &data, sizeof data);
-
-	if (detached)
-		return (client_flush(&cctx));
-	return (client_main(&cctx));
-}
-
-int
-op_attach(char *path, int argc, char **argv)
-{
-	struct attach_data	data;
-	struct client_ctx	cctx;
-	char			name[MAXNAMELEN];
-	int			opt;
-
-	*name = '\0';
-	optind = 1;
-	while ((opt = getopt(argc, argv, "s:?")) != EOF) {
-		switch (opt) {
-		case 's':
-			if (strlcpy(name, optarg, sizeof name) >= sizeof name) {
-				log_warnx("session name too long: %s", optarg);
-				return (1);
-			}
-			break;
-		case '?':
-		default:
-			return (usage("attach [-s session]"));
-		}
-	}	
-	argc -= optind;
-	argv += optind;			
-	if (argc != 0)
-		return (usage("attach [-s session]"));
-
-	if (client_init(path, &cctx, 1) != 0)
-		return (1);
-
-	client_fill_sessid(&data.sid, name);
-	data.sx = cctx.ws.ws_col;
-	data.sy = cctx.ws.ws_row;
-	client_write_server(&cctx, MSG_ATTACH, &data, sizeof data);
-
-	return (client_main(&cctx));
-}
-
-int
-op_rename_window(char *path, int argc, char **argv)
-{
-	struct rename_data	data;	
-	struct client_ctx	cctx;
-	char			sname[MAXNAMELEN];
-	int			opt;  
-	const char	       *errstr;
-
-	*sname = '\0';
-	data.idx = -1;
-	optind = 1;
-	while ((opt = getopt(argc, argv, "i:s:?")) != EOF) {
-		switch (opt) {
-		case 's':
-			if (strlcpy(sname, optarg, sizeof sname) 
-			    >= sizeof sname) {
-				log_warnx("session name too long: %s", optarg);
-				return (1);
-			}
-			break;
-		case 'i':
-			data.idx = strtonum(optarg, 0, INT_MAX, &errstr);
-			if (errstr != NULL) {
-				log_warnx(
-				    "window index %s: %s", errstr, optarg); 
-				return (1);
-			}
-			break;
-		case '?':
-		default:
-			return (usage(
-			    "rename-window [-s session] [-i index] name"));
-		}
-	}
-	argc -= optind;
-	argv += optind;
-	if (argc != 1)
-		return (usage("rename-window [-s session] [-i index] name"));
-
-	if (client_init(path, &cctx, 1) != 0)
-		return (1);
-
-	client_fill_sessid(&data.sid, sname);
-	if ((strlcpy(data.newname, argv[0], sizeof data.newname) 
-	    >= sizeof data.newname)) {
-		log_warnx("new window name too long: %s", argv[0]);
-		return (1);
-	}
-	client_write_server(&cctx, MSG_RENAME, &data, sizeof data);
-
-	return (client_flush(&cctx));
-}
-
-int
-op_bind_key(char *path, int argc, char **argv)
-{
-	struct bind_data	data;	
-	struct client_ctx	cctx;
-	int			opt;
-	const char	       *errstr;
-	char		       *str;
-	size_t			len;
- 	const struct bind      *bind;
-	
-	optind = 1;
-	while ((opt = getopt(argc, argv, "?")) != EOF) {
-		switch (opt) {
-		default:
-			return (usage("bind-key key command [argument]"));
-		}
-	}
-	argc -= optind;
-	argv += optind;
-	if (argc != 2 && argc != 3)
-		return (usage("bind-key key command [argument]"));
-
-	if ((data.key = key_string_lookup(argv[0])) == KEYC_NONE) {
-		log_warnx("unknown key: %s", argv[0]);
-		return (1);
-	}
-	if (strlcpy(data.cmd, argv[1], sizeof data.cmd) >= sizeof data.cmd) {
-		log_warnx("command too long: %s", argv[1]);
-		return (1);
-	}
-
-	if ((bind = cmd_lookup_bind(data.cmd)) == NULL) {
-		log_warnx("unknown command: %s", data.cmd);
-		return (1);
-	}
-
-	str = NULL;
-	len = 0;
-	if (bind->flags & BIND_USER) {
-		if (argc != 3) {
-			log_warnx("%s requires an argument", data.cmd);
-			return (1);
-		}
-
-		data.flags |= BIND_USER;
-		if (bind->flags & BIND_STRING) {
-			data.flags |= BIND_STRING;
-			str = argv[2];
-			len = strlen(str);
-		} else if (bind->flags & BIND_NUMBER) {
-			data.flags |= BIND_NUMBER;
-			data.num = strtonum(argv[2], 0, UINT_MAX, &errstr);
-			if (errstr != NULL) {
-				log_warnx("argument %s: %s", errstr, argv[2]); 
-				return (1);
-			}
-		} else
-			fatalx("no argument type");
-	} else {
-		if (argc != 2) {
-			log_warnx("%s cannot have an argument", data.cmd);
-			return (1);
-		}
-
-		data.flags = 0;
-	}
-
-	if (client_init(path, &cctx, 1) != 0)
-		return (1);
-
-	client_write_server2(&cctx, MSG_BINDKEY, &data, sizeof data, str, len);
-
-	return (client_flush(&cctx));
-}
-
-int
-op_unbind_key(char *path, int argc, char **argv)
-{
-	struct bind_data	data;	
-	struct client_ctx	cctx;
-	int			opt;
-
-	optind = 1;
-	while ((opt = getopt(argc, argv, "?")) != EOF) {
-		switch (opt) {
-		default:
-			return (usage("unbind-key key"));
-		}
-	}
-	argc -= optind;
-	argv += optind;
-	if (argc != 1)
-		return (usage("unbind-key key"));
-
-	if ((data.key = key_string_lookup(argv[0])) == KEYC_NONE) {
-		log_warnx("unknown key: %s", argv[0]);
-		return (1);
-	}
-
-	if (client_init(path, &cctx, 1) != 0)
-		return (1);
-
-	client_write_server(&cctx, MSG_UNBINDKEY, &data, sizeof data);
-
-	return (client_flush(&cctx));
-}
diff --git a/server-fn.c b/server-fn.c
index f46a4715..7af77146 100644
--- a/server-fn.c
+++ b/server-fn.c
@@ -1,4 +1,4 @@
-/* $Id: server-fn.c,v 1.13 2007-10-03 12:34:16 nicm Exp $ */
+/* $Id: server-fn.c,v 1.14 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -64,7 +64,7 @@ server_find_sessid(struct sessid *sid, char **cause)
 		}
 	}
 	if (s == NULL) {
-		xasprintf(cause, "no sessions");
+		xasprintf(cause, "no sessions found");
 		return (NULL);
 	}
 	if (n != 1) {
@@ -125,6 +125,8 @@ server_write_clients(
 	for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
 		c = ARRAY_ITEM(&clients, i);
 		if (c != NULL && c->session != NULL) {
+			if (c->flags & CLIENT_HOLD) /* XXX OUTPUT only */
+				continue;
 			if (c->session->window == w) {
 				log_debug(
 				    "writing %d to clients: %d", type, c->fd);
@@ -145,21 +147,22 @@ server_window_changed(struct client *c)
 	w = c->session->window;
 	if (c->sx != w->screen.sx || c->sy != w->screen.sy)
 		window_resize(w, c->sx, c->sy);
-	server_draw_client(c, 0, c->sy - 1);
+	server_draw_client(c);
 }
 
 /* Draw window on client. */
 void
-server_draw_client(struct client *c, u_int py_upper, u_int py_lower)
+server_draw_client(struct client *c)
 {
-	struct hdr	hdr;
-	size_t		size;
+	struct hdr	 hdr;
+	size_t		 size;
+	struct screen	*s = &c->session->window->screen;
 
 	buffer_ensure(c->out, sizeof hdr);
 	buffer_add(c->out, sizeof hdr);
 	size = BUFFER_USED(c->out);
 
-	screen_draw(&c->session->window->screen, c->out, py_upper, py_lower);
+	screen_draw(s, c->out, 0, s->sy - 1);
 
 	size = BUFFER_USED(c->out) - size;
 	log_debug("redrawing screen, %zu bytes", size);
diff --git a/server-msg.c b/server-msg.c
index 6de3d34c..89d57b0f 100644
--- a/server-msg.c
+++ b/server-msg.c
@@ -1,4 +1,4 @@
-/* $Id: server-msg.c,v 1.19 2007-10-03 13:07:42 nicm Exp $ */
+/* $Id: server-msg.c,v 1.20 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -24,16 +24,13 @@
 
 #include "tmux.h"
 
-int	server_msg_fn_attach(struct hdr *, struct client *);
-int	server_msg_fn_bindkey(struct hdr *, struct client *);
+int	server_msg_fn_command(struct hdr *, struct client *);
+int	server_msg_fn_identify(struct hdr *, struct client *);
 int	server_msg_fn_keys(struct hdr *, struct client *);
-int	server_msg_fn_new(struct hdr *, struct client *);
-int	server_msg_fn_rename(struct hdr *, struct client *);
-int	server_msg_fn_sessions(struct hdr *, struct client *);
-int	server_msg_fn_size(struct hdr *, struct client *);
-int	server_msg_fn_unbindkey(struct hdr *, struct client *);
-int	server_msg_fn_windowlist(struct hdr *, struct client *);
-int	server_msg_fn_windows(struct hdr *, struct client *);
+int	server_msg_fn_resize(struct hdr *, struct client *);
+
+void	server_msg_fn_command_error(struct cmd_ctx *, const char *, ...);
+void	server_msg_fn_command_print(struct cmd_ctx *, const char *, ...);
 
 struct server_msg {
 	enum hdrtype	type;
@@ -41,16 +38,10 @@ struct server_msg {
 	int	        (*fn)(struct hdr *, struct client *);
 };
 const struct server_msg server_msg_table[] = {
-	{ MSG_ATTACH, server_msg_fn_attach },
-	{ MSG_BINDKEY, server_msg_fn_bindkey },
+	{ MSG_IDENTIFY, server_msg_fn_identify },
+	{ MSG_COMMAND, server_msg_fn_command },
+	{ MSG_RESIZE, server_msg_fn_resize },
 	{ MSG_KEYS, server_msg_fn_keys },
-	{ MSG_NEW, server_msg_fn_new },
-	{ MSG_RENAME, server_msg_fn_rename },
-	{ MSG_SESSIONS, server_msg_fn_sessions },
-	{ MSG_SIZE, server_msg_fn_size },
-	{ MSG_UNBINDKEY, server_msg_fn_unbindkey },
-	{ MSG_WINDOWLIST, server_msg_fn_windowlist },
-	{ MSG_WINDOWS, server_msg_fn_windows },
 };
 #define NSERVERMSG (sizeof server_msg_table / sizeof server_msg_table[0])
 
@@ -83,89 +74,98 @@ server_msg_dispatch(struct client *c)
 	}
 }
 
-/* New message from client. */
-int
-server_msg_fn_new(struct hdr *hdr, struct client *c)
+void
+server_msg_fn_command_error(struct cmd_ctx *ctx, const char *fmt, ...)
 {
-	struct new_data	 data;
-	char	         *msg;
-	
-	if (c->session != NULL)
-		return (0);
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_NEW size");
+	va_list	ap;
+	char   *msg;
+
+	va_start(ap, fmt);
+	xvasprintf(&msg, fmt, ap);
+	va_end(ap);
+
+	server_write_client(ctx->client, MSG_ERROR, msg, strlen(msg));
+	xfree(msg);
+}
+
+void
+server_msg_fn_command_print(struct cmd_ctx *ctx, const char *fmt, ...)
+{
+	va_list	ap;
+	char   *msg;
+
+	va_start(ap, fmt);
+	xvasprintf(&msg, fmt, ap);
+	va_end(ap);
+
+	server_write_client(ctx->client, MSG_PRINT, msg, strlen(msg));
+	xfree(msg);
+}
+
+int
+server_msg_fn_command(struct hdr *hdr, struct client *c)
+{
+	struct msg_command_data	data;
+	struct cmd_ctx	 	ctx;
+	struct cmd	       *cmd;
+	char	       	       *cause;
+
+	if (hdr->size < sizeof data)
+		fatalx("bad MSG_COMMAND size");
 	buffer_read(c->in, &data, sizeof data);
 
-	c->sx = data.sx;
-	if (c->sx == 0)
-		c->sx = 80;
-	c->sy = data.sy;
-	if (c->sy == 0)
-		c->sy = 25;
+	cmd = cmd_recv(c->in);
+	log_debug("got command %u %s from client %d",
+	    cmd->entry->type, cmd->entry->name, c->fd);
 
-	if (c->sy >= status_lines)
-		c->sy -= status_lines;
+	if (cmd->entry->flags & CMD_NOSESSION)
+		ctx.session = NULL;
+	else {
+		ctx.session = server_find_sessid(&data.sid, &cause);
+		if (ctx.session == NULL) {
+			server_write_error(c, "%s", cause);
+			xfree(cause);
+			return (0);
+		}
+	}		
 
-	data.name[(sizeof data.name) - 1] = '\0';
-	if (*data.name != '\0' && session_find(data.name) != NULL) {
-		xasprintf(&msg, "duplicate session: %s", data.name);
-		server_write_client(c, MSG_ERROR, msg, strlen(msg));
-		xfree(msg);
-		return (0);
-	}
+	ctx.error = server_msg_fn_command_error;
+	ctx.print = server_msg_fn_command_print;
 
-	c->session = session_create(data.name, default_command, c->sx, c->sy);
-	if (c->session == NULL)
-		fatalx("session_create failed");
+	ctx.client = c;
+	ctx.flags = 0;
 
-	server_write_client(c, MSG_OKAY, NULL, 0);
-	server_draw_client(c, 0, c->sy - 1);
+	cmd_exec(cmd, &ctx);
+	cmd_free(cmd);
 
 	return (0);
 }
 
-/* Attach message from client. */
 int
-server_msg_fn_attach(struct hdr *hdr, struct client *c)
+server_msg_fn_identify(struct hdr *hdr, struct client *c)
 {
-	struct attach_data	 data;
-	char			*cause;
-	
-	if (c->session != NULL)
-		return (0);
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_ATTACH size");
+	struct msg_identify_data	data;
+
+	if (hdr->size < sizeof data)
+		fatalx("bad MSG_IDENTIFY size");
 	buffer_read(c->in, &data, sizeof data);
 
+	log_debug("got identify msg from client: %u,%u", data.sx, data.sy);
+
 	c->sx = data.sx;
-	if (c->sx == 0)
-		c->sx = 80;
 	c->sy = data.sy;
-	if (c->sy == 0)
-		c->sy = 25;
 
-	if (c->sy >= status_lines)
-		c->sy -= status_lines;
-
-	if ((c->session = server_find_sessid(&data.sid, &cause)) == NULL) {
-		server_write_error(c, "%s", cause);
-		xfree(cause);
-		return (0);
-	}
-
-	server_draw_client(c, 0, c->sy - 1);
+	c->flags |= CLIENT_TERMINAL;
 
 	return (0);
 }
 
-/* Size message from client. */
 int
-server_msg_fn_size(struct hdr *hdr, struct client *c)
+server_msg_fn_resize(struct hdr *hdr, struct client *c)
 {
-	struct size_data	data;
+	struct msg_resize_data	data;
+	u_int			sy;
 
-	if (c->session == NULL)
-		return (0);
 	if (hdr->size != sizeof data)
 		fatalx("bad MSG_SIZE size");
 	buffer_read(c->in, &data, sizeof data);
@@ -177,265 +177,47 @@ server_msg_fn_size(struct hdr *hdr, struct client *c)
 	if (c->sy == 0)
 		c->sy = 25;
 
-	if (c->sy >= status_lines)
-		c->sy -= status_lines;
+	sy = c->sy;
+	if (sy < status_lines)
+		sy = status_lines + 1;
+	sy -= status_lines;
 
-	if (window_resize(c->session->window, c->sx, c->sy) != 0)
-		server_draw_client(c, 0, c->sy - 1);
+	if (window_resize(c->session->window, c->sx, sy) != 0)
+		server_draw_client(c);
 
 	return (0);
 }
 
-/* Keys message from client. */
 int
 server_msg_fn_keys(struct hdr *hdr, struct client *c)
 {
 	int	key;
 	size_t	size;
 
-	if (c->session == NULL)
-		return (0);
 	if (hdr->size & 0x1)
 		fatalx("bad MSG_KEYS size");
 
+	if (c->flags & CLIENT_HOLD) {
+		server_draw_client(c);
+		c->flags &= ~CLIENT_HOLD;
+	}
+
 	size = hdr->size;
 	while (size != 0) {
 		key = (int16_t) input_extract16(c->in);
 		size -= 2;
 
-		if (c->prefix) {
-			cmd_dispatch(c, key);
-			c->prefix = 0;
+		if (c->flags & CLIENT_PREFIX) {
+			key_bindings_dispatch(key, c);
+			c->flags &= ~CLIENT_PREFIX;
 			continue;
 		}
 
-		if (key == cmd_prefix)
-			c->prefix = 1;
+		if (key == prefix_key)
+			c->flags |= CLIENT_PREFIX;
 		else
 			window_key(c->session->window, key);
 	}
 
 	return (0);
 }
-
-/* Sessions message from client. */
-int
-server_msg_fn_sessions(struct hdr *hdr, struct client *c)
-{
-	struct sessions_data	 data;
-	struct sessions_entry	 entry;
-	struct session		*s;
-	u_int			 i, j;
-
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_SESSIONS size");
-	buffer_read(c->in, &data, sizeof data);
-
-	data.sessions = 0;
-	for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
-		if (ARRAY_ITEM(&sessions, i) != NULL)
-			data.sessions++;
-	}
-	server_write_client2(c, MSG_SESSIONS,
-	    &data, sizeof data, NULL, data.sessions * sizeof entry);
-
-	for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
-		s = ARRAY_ITEM(&sessions, i);
-		if (s == NULL)
-			continue;
-		strlcpy(entry.name, s->name, sizeof entry.name);
-		entry.tim = s->tim;
-		entry.windows = 0;
-		for (j = 0; j < ARRAY_LENGTH(&s->windows); j++) {
-			if (ARRAY_ITEM(&s->windows, j) != NULL)
-				entry.windows++;
-		}
-		buffer_write(c->out, &entry, sizeof entry);
-	}
-
-	return (0);
-}
-
-/* Windows message from client. */
-int
-server_msg_fn_windows(struct hdr *hdr, struct client *c)
-{
-	struct windows_data	 data;
-	struct windows_entry	 entry;
-	struct session		*s;
-	struct window		*w;
-	u_int			 i;
-	char		 	*cause;
-
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_WINDOWS size");
-	buffer_read(c->in, &data, sizeof data);
-
-	if ((s = server_find_sessid(&data.sid, &cause)) == NULL) {
-		server_write_error(c, "%s", cause);
-		xfree(cause);
-		return (0);
-	}
-
-	data.windows = 0;
-	for (i = 0; i < ARRAY_LENGTH(&s->windows); i++) {
-		if (ARRAY_ITEM(&s->windows, i) != NULL)
-			data.windows++;
-	}
-	server_write_client2(c, MSG_WINDOWS,
-	    &data, sizeof data, NULL, data.windows * sizeof entry);
-	
-	for (i = 0; i < ARRAY_LENGTH(&s->windows); i++) {
-		w = ARRAY_ITEM(&s->windows, i);
-		if (w == NULL)
-			continue;
-		entry.idx = i;
-		strlcpy(entry.name, w->name, sizeof entry.name);
-		strlcpy(entry.title, w->screen.title, sizeof entry.title);
-		if (ttyname_r(w->fd, entry.tty, sizeof entry.tty) != 0)
-			*entry.tty = '\0';
-		buffer_write(c->out, &entry, sizeof entry);
-	}
-
-	return (0);
-}
-
-/* Rename message from client. */
-int
-server_msg_fn_rename(struct hdr *hdr, struct client *c)
-{
-	struct rename_data	data;
-	char                   *cause;
-	struct window	       *w;
-	struct session	       *s;
-	u_int			i;
-
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_RENAME size");
-	buffer_read(c->in, &data, sizeof data);
-
- 	data.newname[(sizeof data.newname) - 1] = '\0';
-	if ((s = server_find_sessid(&data.sid, &cause)) == NULL) {
-		server_write_error(c, "%s", cause);
-		xfree(cause);
-		return (0);
-	}
-
-	if (data.idx == -1)
-		w = s->window;
-	else {
-		if (data.idx < 0)
-			fatalx("bad window index");
-		w = window_at(&s->windows, data.idx);
-		if (w == NULL) { 
-			server_write_error(c, "window not found: %d", data.idx);
-			return (0);
-		}
-	}
-
-	strlcpy(w->name, data.newname, sizeof w->name);
-
-	server_write_client(c, MSG_OKAY, NULL, 0);
-	for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
-		c = ARRAY_ITEM(&clients, i);
-		if (c != NULL && c->session != NULL) {
-			if (session_has(c->session, w))
-				server_draw_status(c);
-		}
-	}
-
-	return (0);
-}
-
-/* Window list message from client */
-int
-server_msg_fn_windowlist(struct hdr *hdr, struct client *c)
-{
-	struct window	*w;
-	char 		*buf;
-	size_t		 len, off;
-	u_int 		 i;
-
-	if (c->session == NULL)
-		return (0);
-	if (hdr->size != 0)
-		fatalx("bad MSG_WINDOWLIST size");
-
-	len = c->sx + 1;
-	buf = xmalloc(len);
-	off = 0;
-
-	*buf = '\0';
-	for (i = 0; i < ARRAY_LENGTH(&c->session->windows); i++) {
-		w = ARRAY_ITEM(&c->session->windows, i);
-		if (w == NULL)
-			continue;
-		off += xsnprintf(buf + off, len - off, "%u:%s%s ", i, w->name, 
-		    w == c->session->window ? "*" : "");
-		if (off >= len)
-			break;
-	}
-
-	server_write_message(c, "%s", buf);
-	xfree(buf);
-
-	return (0);
-}
-
-/* Bind key message from client */
-int
-server_msg_fn_bindkey(struct hdr *hdr, struct client *c)
-{
-	struct bind_data	data;
-	const struct bind      *bind;
-	char		       *str;
-
-	if (hdr->size < sizeof data)
-		fatalx("bad MSG_BINDKEY size");
-	buffer_read(c->in, &data, sizeof data);
-
-	str = NULL;
-	if (data.flags & BIND_STRING) {
-		hdr->size -= sizeof data;
-
-		str = xmemstrdup(BUFFER_OUT(c->in), hdr->size);
-		if (hdr->size > 0)
-			buffer_remove(c->in, hdr->size);
-	}
-
- 	data.cmd[(sizeof data.cmd) - 1] = '\0';	
-	if ((bind = cmd_lookup_bind(data.cmd)) == NULL)
-		fatalx("unknown command");
-	if (!(bind->flags & BIND_USER) &&
-	    (data.flags & (BIND_NUMBER|BIND_STRING)) != 0)
-		fatalx("argument missing");
-	if ((bind->flags & BIND_USER) &&
-	    (data.flags & (BIND_NUMBER|BIND_STRING)) == 0)
-		fatalx("argument required");
-	
-	cmd_add_bind(data.key, data.num, str, bind);
-	if (str != NULL)
-		xfree(str);
-
-	server_write_client(c, MSG_OKAY, NULL, 0);
-
-	return (0);
-}
-
-/* Unbind key message from client */
-int
-server_msg_fn_unbindkey(struct hdr *hdr, struct client *c)
-{
-	struct bind_data	data;
-
-	if (hdr->size != sizeof data)
-		fatalx("bad MSG_UNBINDKEY size");
-
-	buffer_read(c->in, &data, hdr->size);
-
-	cmd_remove_bind(data.key);
-
-	server_write_client(c, MSG_OKAY, NULL, 0);
-
-	return (0);
-}
diff --git a/server.c b/server.c
index 8cdbd793..8fe25f5a 100644
--- a/server.c
+++ b/server.c
@@ -1,4 +1,4 @@
-/* $Id: server.c,v 1.19 2007-10-03 11:26:34 nicm Exp $ */
+/* $Id: server.c,v 1.20 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -127,7 +127,7 @@ server_main(char *srv_path, int srv_fd)
 	ARRAY_INIT(&clients);
 	ARRAY_INIT(&sessions);
 
-	cmd_init();
+	key_bindings_init();
 
 	pfds = NULL;
 	while (!sigterm) {
@@ -171,7 +171,7 @@ server_main(char *srv_path, int srv_fd)
 		server_handle_clients(&pfd);
 	}
 
-	cmd_free();
+	key_bindings_free();
 
 	close(srv_fd);
 	unlink(srv_path);
diff --git a/session.c b/session.c
index a12f715e..69b528ed 100644
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $Id: session.c,v 1.19 2007-09-29 21:02:26 nicm Exp $ */
+/* $Id: session.c,v 1.20 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -64,11 +64,10 @@ session_create(const char *name, const char *cmd, u_int sx, u_int sy)
 	if (i == ARRAY_LENGTH(&sessions))
 		ARRAY_ADD(&sessions, s);
 
-	if (*name != '\0')
-		strlcpy(s->name, name, sizeof s->name);
+	if (name != NULL)
+		s->name = xstrdup(name);
 	else
-		xsnprintf(s->name, sizeof s->name, "%u", i);
-
+		xasprintf(&s->name, "%u", i);
 	if (session_new(s, cmd, sx, sy) != 0) {
 		session_destroy(s);
 		return (NULL);
@@ -92,6 +91,7 @@ session_destroy(struct session *s)
 	while (!ARRAY_EMPTY(&s->windows))
 		window_remove(&s->windows, ARRAY_FIRST(&s->windows));
 
+	xfree(s->name);
 	xfree(s);
 }
 
diff --git a/tmux.c b/tmux.c
index 13dbbb0e..b47bff65 100644
--- a/tmux.c
+++ b/tmux.c
@@ -1,4 +1,4 @@
-/* $Id: tmux.c,v 1.24 2007-10-03 12:56:02 nicm Exp $ */
+/* $Id: tmux.c,v 1.25 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -19,7 +19,10 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 
+#include <err.h>
+#include <errno.h>
 #include <paths.h>
+#include <poll.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
@@ -35,45 +38,29 @@ const char	*malloc_options = "AFGJPX";
 volatile sig_atomic_t sigwinch;
 volatile sig_atomic_t sigterm;
 int		 debug_level;
+int		 prefix_key = META;
 u_int		 status_lines;
 char		*default_command;
 
 void		 sighandler(int);
 
-struct op {
-	const char     *cmd;
-	const char     *alias;
-	int		(*fn)(char *, int, char **);
-};
-const struct op op_table[] = {
-	{ "attach", NULL, op_attach },
-	{ "bind-key", "bind", op_bind_key },
-	{ "list-sessions", "ls", op_list_sessions },
-	{ "list-windows", "lsw", op_list_windows },
-	{ "new-session", "new", op_new_session },
-	{ "rename-window", "renw", op_rename_window },
-	{ "unbind-key", "unbind", op_unbind_key },
-};
-#define NOP (sizeof op_table / sizeof op_table[0])
-
-int
-usage(const char *fmt, ...)
+void
+usage(char **ptr, const char *fmt, ...)
 {
 	char	*msg;
 	va_list	 ap;
 
 	if (fmt == NULL) {
-		fprintf(stderr,
-		    "usage: %s [-v] [-S path] command [flags]\n", __progname);
-		return (1);
-	}
+		xasprintf(ptr,
+		    "usage: %s [-v] [-S path] command [flags]", __progname);
+	} else {
+		va_start(ap, fmt);
+		xvasprintf(&msg, fmt, ap);
+		va_end(ap);
 
-	va_start(ap, fmt);
-	xvasprintf(&msg, fmt, ap);
-	va_end(ap);
-	fprintf(stderr, "usage: %s [-v] [-S path] %s\n", __progname, msg);
-	xfree(msg);
-	return (1);
+		xasprintf(ptr, "usage: %s [-v] [-S path] %s", __progname, msg);
+		xfree(msg);
+	}
 }
 
 void
@@ -172,30 +159,39 @@ sigreset(void)
 int
 main(int argc, char **argv)
 {
-	const struct op		*op, *found;
+	struct client_ctx	 cctx;
+	struct msg_command_data	 data;
+	struct buffer		*b;
+	struct cmd		*cmd;
+	struct pollfd	 	 pfd;
+	struct hdr	 	 hdr;
 	const char		*shell;
-	char			*path;
+	char			*path, *cause, name[MAXNAMELEN];
 	int	 		 opt;
-	u_int			 i;
 
+	*name = '\0';
 	path = NULL;
-        while ((opt = getopt(argc, argv, "S:v?")) != EOF) {
+        while ((opt = getopt(argc, argv, "S:s:v?")) != EOF) {
                 switch (opt) {
 		case 'S':
 			path = xstrdup(optarg);
 			break;
+		case 's':
+			if (strlcpy(name, optarg, sizeof name) >= sizeof name)
+				errx(1, "session name too long: %s", optarg);
+			break;
 		case 'v':
 			debug_level++;
 			break;
                 case '?':
                 default:
-                        exit(usage(NULL));
+			goto usage;
                 }
         }
 	argc -= optind;
 	argv += optind;
 	if (argc == 0)
-		exit(usage(NULL));
+		goto usage;
 
 	log_open(stderr, LOG_USER, debug_level);
 
@@ -206,21 +202,75 @@ main(int argc, char **argv)
 		shell = "/bin/ksh";
 	xasprintf(&default_command, "exec %s -l", shell);
 
-	found = NULL;
-	for (i = 0; i < NOP; i++) {
-		op = op_table + i;
-		if (op->alias != NULL && strcmp(argv[0], op->alias) == 0)
-			exit(op->fn(path, argc, argv));
-		if (strncmp(argv[0], op->cmd, strlen(argv[0])) == 0) {
-			if (found != NULL) {
-				log_warnx("ambiguous command: %s", argv[0]);
-				exit(1);
-			}
-			found = op;
+	if ((cmd = cmd_parse(argc, argv, &cause)) == NULL) {
+		if (cause == NULL)
+			goto usage;
+		log_warnx("%s", cause);
+		exit(1);
+	}
+
+	if (!(cmd->entry->flags & CMD_NOSESSION))
+		client_fill_sessid(&data.sid, name);
+	if (client_init(path, &cctx, cmd->entry->flags & CMD_STARTSERVER) != 0)
+		exit(1);
+	b = buffer_create(BUFSIZ);
+	cmd_send(cmd, b);
+	cmd_free(cmd);
+
+	client_write_server2(&cctx,
+	    MSG_COMMAND, &data, sizeof data, BUFFER_OUT(b), BUFFER_USED(b));
+	buffer_destroy(b);
+	
+	for (;;) {
+		pfd.fd = cctx.srv_fd;
+		pfd.events = POLLIN;
+		if (BUFFER_USED(cctx.srv_out) > 0)
+			pfd.events |= POLLOUT;
+	
+		if (poll(&pfd, 1, INFTIM) == -1) {
+			if (errno == EAGAIN || errno == EINTR)
+				continue;
+			fatal("poll failed");
+		}
+
+		if (buffer_poll(&pfd, cctx.srv_in, cctx.srv_out) != 0)
+			fatalx("lost server");
+
+	restart:
+		if (BUFFER_USED(cctx.srv_in) < sizeof hdr)
+			continue;
+		memcpy(&hdr, BUFFER_OUT(cctx.srv_in), sizeof hdr);
+		if (BUFFER_USED(cctx.srv_in) < (sizeof hdr) + hdr.size)
+			continue;
+		buffer_remove(cctx.srv_in, sizeof hdr);
+
+		switch (hdr.type) {
+		case MSG_EXIT:
+			exit(0);
+		case MSG_PRINT:
+			if (hdr.size > INT_MAX - 1)
+				fatalx("bad MSG_PRINT size");
+			log_info(
+			    "%.*s", (int) hdr.size, BUFFER_OUT(cctx.srv_in));
+			buffer_remove(cctx.srv_in, hdr.size);
+			goto restart;
+		case MSG_ERROR:
+			if (hdr.size > INT_MAX - 1)
+				fatalx("bad MSG_ERROR size");
+			log_warnx("%s: %.*s", __progname,
+			    (int) hdr.size, BUFFER_OUT(cctx.srv_in));	
+			buffer_remove(cctx.srv_in, hdr.size);
+			exit(1);
+		case MSG_READY:
+			exit(client_main(&cctx));
+		default:
+			fatalx("unexpected command");
 		}
 	}
-	if (found != NULL)
-		exit(found->fn(path, argc, argv));
+	/* NOTREACHED */
 
-	exit(usage(NULL));
+usage:
+	usage(&cause, NULL);
+	fprintf(stderr, "%s\n", cause);
+	exit(1);
 }
diff --git a/tmux.h b/tmux.h
index 97b5df8c..0169b161 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1,4 +1,4 @@
-/* $Id: tmux.h,v 1.37 2007-10-03 12:43:47 nicm Exp $ */
+/* $Id: tmux.h,v 1.38 2007-10-03 21:31:07 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -259,28 +259,17 @@ struct buffer {
 
 /* Message codes. */
 enum hdrtype {
-	MSG_ATTACH,
-	MSG_DATA,
-	MSG_DETACH,
+	MSG_COMMAND,
 	MSG_ERROR,
+	MSG_PRINT,
 	MSG_EXIT,
+	MSG_IDENTIFY,
+	MSG_READY,
+	MSG_DETACH,
+	MSG_RESIZE,
+	MSG_DATA,
 	MSG_KEYS,
-	MSG_NEW,
-	MSG_OKAY,
 	MSG_PAUSE,
-	MSG_RENAME,
-	MSG_SESSIONS,
-	MSG_SIZE,
-	MSG_WINDOWLIST,
-	MSG_WINDOWS,
-	MSG_BINDKEY,
-	MSG_UNBINDKEY,
-};
-
-/* Message header structure. */
-struct hdr {
-	enum hdrtype	type;
-	size_t		size;
 };
 
 /* Session identification. */
@@ -290,61 +279,26 @@ struct sessid {
 	char		name[MAXNAMELEN];	/* empty for current */
 };
 
-struct new_data {
-	char		name[MAXNAMELEN];
+/* Message header structure. */
+struct hdr {
+	enum hdrtype	type;
+	size_t		size;
+};
+
+struct msg_command_data {
+	struct sessid   sid;
+};
+
+struct msg_identify_data {
 	u_int		sx;
 	u_int		sy;
 };
 
-struct attach_data {
-	struct sessid	sid;
+struct msg_resize_data {
 	u_int		sx;
 	u_int		sy;
 };
 
-struct sessions_data {
-	u_int		sessions;
-};
-
-struct sessions_entry {
-	char		name[MAXNAMELEN];
-	time_t		tim;
-	u_int		windows;
-};
-
-struct windows_data {
-	struct sessid	sid;
-	u_int		windows;
-};
-
-struct windows_entry {
-	u_int		idx;
-	char		tty[TTY_NAME_MAX];
-
-	char		name[MAXNAMELEN];
-	char		title[MAXTITLELEN];
-};
-
-struct size_data {
-	u_int		sx;
-	u_int		sy;
-};
-
-struct rename_data {
-	int		idx;
-	struct sessid	sid;
-	char		newname[MAXNAMELEN];
-};
-
-struct bind_data {
-	int		key;
-	char		cmd[MAXNAMELEN];
-
-	int		flags;
-
-	u_int		num;
-};
-
 /* Attributes. */
 #define ATTR_BRIGHT 0x1
 #define ATTR_DIM 0x2
@@ -460,7 +414,7 @@ ARRAY_DECL(windows, struct window *);
 
 /* Client session. */
 struct session {
-	char		 name[MAXNAMELEN];
+	char		*name;
 	time_t		 tim;
 
 	struct window	*window;
@@ -478,7 +432,10 @@ struct client {
 	u_int		 sx;
 	u_int		 sy;
 
-	int		 prefix;	/* waiting for command */
+#define CLIENT_TERMINAL 0x1
+#define CLIENT_PREFIX 0x2
+#define CLIENT_HOLD 0x4
+	int		 flags;
 
 	struct session	*session;
 };
@@ -493,50 +450,85 @@ struct client_ctx {
 	int		 loc_fd;
 	struct buffer	*loc_in;
 	struct buffer	*loc_out;
-
-	struct winsize	 ws;
 };
 
-/* Key command. */
+/* Key/command line command. */
+enum cmd_type {
+	CMD_NEWSESSION,
+	CMD_DETACHSESSION,
+	CMD_LISTSESSIONS,
+};
+
+struct cmd_ctx {
+	struct client  *client;
+	struct session *session;
+
+	void		(*print)(struct cmd_ctx *, const char *, ...);
+	void		(*error)(struct cmd_ctx *, const char *, ...);
+
+#define CMD_KEY 0x1
+	int		flags;
+};
+
+struct cmd_entry {
+	enum cmd_type	 type;
+	const char	*name;
+	const char	*alias;
+
+#define CMD_STARTSERVER 0x1
+#define CMD_NOSESSION 0x2
+	int		 flags;
+
+	int		 (*parse)(void **, int, char **, char **);
+	const char 	*(*usage)(void);
+	void		 (*exec)(void *, struct cmd_ctx *);
+	void		 (*send)(void *, struct buffer *);
+	void	         (*recv)(void **, struct buffer *);
+	void		 (*free)(void *);
+};
+
 struct cmd {
-	int	key;
-	void	(*fn)(struct client *, struct cmd *);
-	u_int	num;
-	char   *str;
+	const struct cmd_entry *entry;
+	void	       	*data;
 };
 
 /* Key binding. */
-struct bind {
-	const char	*name;
-	void		 (*fn)(struct client *, struct cmd *);
-	
-#define BIND_USER 0x1
-#define BIND_NUMBER 0x2
-#define BIND_STRING 0x4
-	int		 flags;
+struct binding {
+	int		 key;
+	struct cmd	*cmd;
 };
 
 /* tmux.c */
 extern volatile sig_atomic_t sigwinch;
 extern volatile sig_atomic_t sigterm;
+extern int	prefix_key;
 extern int	debug_level;
 extern u_int	status_lines;
 extern char    *default_command;
-int	 	 usage(const char *, ...);
+void		 usage(char **, const char *, ...);
 void		 logfile(const char *);
 void		 siginit(void);
 void		 sigreset(void);
 
-/* op.c */
-int	 op_new_session(char *, int, char **);
-int	 op_attach(char *, int, char **);
-int	 op_rename_window(char *, int, char **);
-int	 op_bind_key(char *, int, char **);
-int	 op_unbind_key(char *, int, char **);
+/* cmd.c */
+struct cmd	*cmd_parse(int, char **, char **);
+void		 cmd_exec(struct cmd *, struct cmd_ctx *);
+void		 cmd_send(struct cmd *, struct buffer *);
+struct cmd	*cmd_recv(struct buffer *);
+void		 cmd_free(struct cmd *);
+void		 cmd_send_string(struct buffer *, const char *);
+char		*cmd_recv_string(struct buffer *);
+extern const struct cmd_entry  cmd_new_session_entry;
+extern const struct cmd_entry  cmd_detach_session_entry;
+extern const struct cmd_entry  cmd_list_sessions_entry;
 
-/* op-list.c */
-int	 op_list_sessions(char *, int, char **);
-int	 op_list_windows(char *, int, char **);
+/* bind.c */
+const struct bind *cmdx_lookup_bind(const char *);
+void	 cmdx_add_bind(int, u_int, char *, const struct bind *);
+void	 cmdx_remove_bind(int);
+void     cmdx_init(void);
+void     cmdx_free(void);
+void	 cmdx_dispatch(struct client *, int);
 
 /* client.c */
 int	 client_init(char *, struct client_ctx *, int);
@@ -552,14 +544,12 @@ void	 client_write_server2(
     	     struct client_ctx *, enum hdrtype, void *, size_t, void *, size_t);
 void	 client_fill_sessid(struct sessid *, char [MAXNAMELEN]);
 
-/* cmd.c */
-extern int cmd_prefix;
-const struct bind *cmd_lookup_bind(const char *);
-void	 cmd_add_bind(int, u_int, char *, const struct bind *);
-void	 cmd_remove_bind(int);
-void     cmd_init(void);
-void     cmd_free(void);
-void	 cmd_dispatch(struct client *, int);
+/* key-bindings.c */
+void	 key_bindings_add(int, struct cmd *);
+void	 key_bindings_remove(int);
+void	 key_bindings_init(void);
+void	 key_bindings_free(void);
+void	 key_bindings_dispatch(int, struct client *);
 
 /* key-string.c */
 int	 key_string_lookup(const char *);
@@ -582,7 +572,7 @@ void	 server_write_client2(struct client *,
 void	 server_write_clients(
     	     struct window *, enum hdrtype, const void *, size_t);
 void	 server_window_changed(struct client *);
-void	 server_draw_client(struct client *, u_int, u_int);
+void	 server_draw_client(struct client *);
 void	 server_draw_status(struct client *);
 
 /* status.c */