From fb39b22a2e7b7c12c56b26abc8ca18f38c2d7bda Mon Sep 17 00:00:00 2001
From: Nicholas Marriott <nicholas.marriott@gmail.com>
Date: Wed, 26 Sep 2007 13:43:15 +0000
Subject: [PATCH] Cleanup part II: split up client stuff and ops. More to come.

---
 Makefile     |   4 +-
 client-cmd.c | 108 +++++++++
 client-msg.c |  99 +++++++++
 client.c     | 251 +++++++++++++++++++++
 command.c    | 113 ----------
 op-list.c    | 126 +++++++++++
 op.c         |  64 ++++++
 server-msg.c | 188 +++++++++-------
 server.c     |  52 ++---
 tmux.c       | 604 ++++++++++-----------------------------------------
 tmux.h       |  58 +++--
 11 files changed, 924 insertions(+), 743 deletions(-)
 create mode 100644 client-cmd.c
 create mode 100644 client-msg.c
 create mode 100644 client.c
 delete mode 100644 command.c
 create mode 100644 op-list.c
 create mode 100644 op.c

diff --git a/Makefile b/Makefile
index 80a3b003..a300e0c4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# $Id: Makefile,v 1.4 2007-09-26 10:35:24 nicm Exp $
+# $Id: Makefile,v 1.5 2007-09-26 13:43:14 nicm Exp $
 
 .SUFFIXES: .c .o .y .h
 .PHONY: clean
@@ -18,7 +18,7 @@ META?= \002 # C-b
 
 SRCS= tmux.c server.c server-msg.c server-fn.c buffer.c buffer-poll.c \
       xmalloc.c xmalloc-debug.c input.c screen.c window.c session.c local.c \
-      log.c command.c
+      log.c client.c client-msg.c client-cmd.c op.c
 
 YACC= yacc -d
 
diff --git a/client-cmd.c b/client-cmd.c
new file mode 100644
index 00000000..998fee2c
--- /dev/null
+++ b/client-cmd.c
@@ -0,0 +1,108 @@
+/* $Id: client-cmd.c,v 1.1 2007-09-26 13:43:14 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 "tmux.h"
+
+int	client_cmd_prefix = META;
+
+int	client_cmd_fn_select(int, struct client_ctx *, const char **);
+int	client_cmd_fn_detach(int, struct client_ctx *, const char **);
+int	client_cmd_fn_msg(int, struct client_ctx *, const char **);
+
+struct cmd {
+	int	key;
+	int	(*fn)(int, struct client_ctx *, const char **);
+	int	arg;
+};
+
+struct cmd client_cmd_table[] = {
+	{ '0', client_cmd_fn_select, 0 },
+	{ '1', client_cmd_fn_select, 1 },
+	{ '2', client_cmd_fn_select, 2 },
+	{ '3', client_cmd_fn_select, 3 },
+	{ '4', client_cmd_fn_select, 4 },
+	{ '5', client_cmd_fn_select, 5 },
+	{ '6', client_cmd_fn_select, 6 },
+	{ '7', client_cmd_fn_select, 7 },
+	{ '8', client_cmd_fn_select, 8 },
+	{ '9', client_cmd_fn_select, 9 },
+	{ 'C', client_cmd_fn_msg, MSG_CREATE },
+	{ 'c', client_cmd_fn_msg, MSG_CREATE },
+	{ 'D', client_cmd_fn_detach, 0 },
+	{ 'd', client_cmd_fn_detach, 0 },
+	{ 'N', client_cmd_fn_msg, MSG_NEXT },
+	{ 'n', client_cmd_fn_msg, MSG_NEXT },
+	{ 'P', client_cmd_fn_msg, MSG_PREVIOUS },
+	{ 'p', client_cmd_fn_msg, MSG_PREVIOUS },
+	{ 'R', client_cmd_fn_msg, MSG_REFRESH },
+	{ 'r', client_cmd_fn_msg, MSG_REFRESH },
+	{ 'T', client_cmd_fn_msg, MSG_RENAME },
+	{ 't', client_cmd_fn_msg, MSG_RENAME },
+	{ 'L', client_cmd_fn_msg, MSG_LAST },
+	{ 'l', client_cmd_fn_msg, MSG_LAST },
+	{ 'W', client_cmd_fn_msg, MSG_WINDOWLIST },
+	{ 'w', client_cmd_fn_msg, MSG_WINDOWLIST }
+};
+#define NCLIENTCMD (sizeof client_cmd_table / sizeof client_cmd_table[0])
+
+/* Dispatch to a command. */
+int
+client_cmd_dispatch(int key, struct client_ctx *cctx, const char **error)
+{
+	struct cmd	*cmd;
+	u_int		 i;
+
+	for (i = 0; i < NCLIENTCMD; i++) {
+		cmd = client_cmd_table + i;
+		if (cmd->key == key)
+			return (cmd->fn(cmd->arg, cctx, error));
+	}
+	return (0);
+}
+
+/* Handle generic command. */
+int
+client_cmd_fn_msg(int arg, struct client_ctx *cctx, unused const char **error)
+{
+	client_write_server(cctx, arg, NULL, 0);
+
+ 	return (0);
+}
+
+/* Handle select command. */
+int
+client_cmd_fn_select(
+    int arg, struct client_ctx *cctx, unused const char **error)
+{
+	struct select_data	data;
+
+	data.idx = arg;
+	client_write_server(cctx, MSG_SELECT, &data, sizeof data);
+
+	return (0);
+}
+
+/* Handle detach command. */
+int
+client_cmd_fn_detach(
+    unused int arg, unused struct client_ctx *cctx, unused const char **error)
+{
+	return (-1);
+}
diff --git a/client-msg.c b/client-msg.c
new file mode 100644
index 00000000..6639d915
--- /dev/null
+++ b/client-msg.c
@@ -0,0 +1,99 @@
+/* $Id: client-msg.c,v 1.1 2007-09-26 13:43:14 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	client_msg_fn_output(struct hdr *, struct client_ctx *, const char **);
+int	client_msg_fn_pause(struct hdr *, struct client_ctx *, const char **);
+int	client_msg_fn_exit(struct hdr *, struct client_ctx *, const char **);
+
+struct client_msg {
+	enum hdrtype   type;
+	
+	int	       (*fn)(struct hdr *, struct client_ctx *, const char **);
+};
+struct client_msg client_msg_table[] = {
+	{ MSG_OUTPUT, client_msg_fn_output },
+	{ MSG_PAUSE, client_msg_fn_pause },
+	{ MSG_EXIT, client_msg_fn_exit },
+};
+#define NCLIENTMSG (sizeof client_msg_table / sizeof client_msg_table[0])
+
+int
+client_msg_dispatch(struct client_ctx *cctx, const char **error)
+{
+	struct hdr		 hdr;
+	struct client_msg	*msg;
+	u_int		 	 i;
+	int			 n;
+
+	for (;;) {
+		if (BUFFER_USED(cctx->srv_in) < sizeof hdr)
+			return (0);
+		memcpy(&hdr, BUFFER_OUT(cctx->srv_in), sizeof hdr);
+		if (BUFFER_USED(cctx->srv_in) < (sizeof hdr) + hdr.size)
+			return (0);
+		buffer_remove(cctx->srv_in, sizeof hdr);
+		
+		for (i = 0; i < NCLIENTMSG; i++) {
+			msg = client_msg_table + i;
+			if (msg->type == hdr.type) {
+				if ((n = msg->fn(&hdr, cctx, error)) != 0)
+					return (n);
+				break;
+			}
+		}
+		if (i == NCLIENTMSG)
+			fatalx("unexpected message");
+	}
+}
+
+/* Output message from client. */
+int
+client_msg_fn_output(
+    struct hdr *hdr, struct client_ctx *cctx, unused const char **error)
+{
+	local_output(cctx->srv_in, hdr->size);
+	return (0);
+}
+
+/* Pause message from server. */
+int
+client_msg_fn_pause(
+    struct hdr *hdr, unused struct client_ctx *cctx, unused const char **error)
+{
+	if (hdr->size != 0)
+		fatalx("bad MSG_PAUSE size");
+	return (1);
+}
+
+/* Exit message from server. */
+int
+client_msg_fn_exit(
+    struct hdr *hdr, unused struct client_ctx *cctx, unused const char **error)
+{
+	if (hdr->size != 0)
+		fatalx("bad MSG_EXIT size");
+	return (-1);
+}
diff --git a/client.c b/client.c
new file mode 100644
index 00000000..8c120aa6
--- /dev/null
+++ b/client.c
@@ -0,0 +1,251 @@
+/* $Id: client.c,v 1.1 2007-09-26 13:43:15 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 <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+void	client_handle_winch(struct client_ctx *);
+int	client_process_local(struct client_ctx *, const char **);
+
+int
+client_init(char *path, struct client_ctx *cctx, int ws)
+{
+	struct sockaddr_un	sa;
+	struct stat		sb;
+	size_t			sz;
+	int			mode;
+
+	if (path == NULL) {
+		xasprintf(&path,
+		    "%s/%s-%lu", _PATH_TMP, __progname, (u_long) getuid());
+	}
+
+retry:
+	if (stat(path, &sb) != 0) {
+		if (errno != ENOENT) {
+			log_warn("%s", path);
+			return (-1);
+		}
+		if (server_start(path) != 0)
+			return (-1);
+		sleep(1); /* XXX */
+		goto retry;
+	}
+	if (!S_ISSOCK(sb.st_mode)) {
+		log_warnx("%s: %s", path, strerror(ENOTSOCK));
+		return (-1);
+	}
+
+	if (ws) {
+		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);
+		}
+	}
+
+	memset(&sa, 0, sizeof sa);
+	sa.sun_family = AF_UNIX;
+	sz = strlcpy(sa.sun_path, path, sizeof sa.sun_path);
+	if (sz >= sizeof sa.sun_path) {
+		log_warnx("%s: %s", path, strerror(ENAMETOOLONG));
+		return (-1);
+	}
+
+	if ((cctx->srv_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+		log_warn("%s: socket", path);
+		return (-1);
+	}
+	if (connect(
+	    cctx->srv_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1) {
+		log_warn("%s: connect", path);
+		return (-1);
+	}
+
+	if ((mode = fcntl(cctx->srv_fd, F_GETFL)) == -1) {
+		log_warn("%s: fcntl", path);
+		return (-1);
+	}
+	if (fcntl(cctx->srv_fd, F_SETFL, mode|O_NONBLOCK) == -1) {
+		log_warn("%s: fcntl", path);
+		return (-1);
+	}
+	cctx->srv_in = buffer_create(BUFSIZ);
+	cctx->srv_out = buffer_create(BUFSIZ);
+
+	return (0);
+}
+
+int
+client_main(struct client_ctx *cctx)
+{
+	struct pollfd	 pfds[2];
+	const char	*error;
+	int		 n;
+
+	logfile("client");
+	setproctitle("client");
+
+	siginit();
+	if ((cctx->loc_fd = local_init(&cctx->loc_in, &cctx->loc_out)) == -1)
+		return (1);
+
+	n = 0;
+	error = NULL;
+	while (!sigterm) {
+		if (sigwinch)
+			client_handle_winch(cctx);
+
+		pfds[0].fd = cctx->srv_fd;
+		pfds[0].events = POLLIN;
+		if (BUFFER_USED(cctx->srv_out) > 0)
+			pfds[0].events |= POLLOUT;
+		pfds[1].fd = cctx->loc_fd;
+		pfds[1].events = POLLIN;
+		if (BUFFER_USED(cctx->loc_out) > 0)
+			pfds[1].events |= POLLOUT;
+	
+		if (poll(pfds, 2, INFTIM) == -1) {
+			if (errno == EAGAIN || errno == EINTR)
+				continue;
+			fatal("poll failed");
+		}
+
+		if (buffer_poll(&pfds[0], cctx->srv_in, cctx->srv_out) != 0)
+			goto server_dead;
+		if (buffer_poll(&pfds[1], cctx->loc_in, cctx->loc_out) != 0)
+			goto local_dead;
+
+		/* XXX Output flushed; pause if required. */
+		if (n)
+			usleep(750000);
+		/* XXX XXX special return code for pause */
+		if ((n = client_process_local(cctx, &error)) == -1)
+			break;
+		if ((n = client_msg_dispatch(cctx, &error)) == -1)
+			break;
+	}
+
+	local_done();
+
+	if (sigterm) 
+		error = "received SIGTERM";
+	if (error != NULL) {
+		printf("[terminated: %s]\n", error);
+		return (0);
+	}
+	printf("[detached]\n");
+	return (0);
+
+server_dead:
+	local_done();
+
+	printf("[lost server]\n");
+	return (1);
+
+local_dead:
+	/* Can't do much here. Log and die. */
+	fatalx("local socket dead");
+}
+
+void
+client_write_server(
+    struct client_ctx *cctx, enum hdrtype type, void *buf, size_t len)
+{
+	struct hdr	hdr;
+
+	hdr.type = type;
+	hdr.size = len;
+	buffer_write(cctx->srv_out, &hdr, sizeof hdr);
+	if (len > 0)
+		buffer_write(cctx->srv_out, buf, len);
+}
+
+void
+client_handle_winch(struct client_ctx *cctx)
+{
+	struct size_data	data;
+
+	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &cctx->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);
+	
+	sigwinch = 0;
+}
+
+int
+client_process_local(struct client_ctx *cctx, const char **error)
+{
+	struct buffer	*b;
+	size_t		 size;
+	int		 n, key;
+
+	n = 0;
+	b = buffer_create(BUFSIZ);
+
+	while ((key = local_key(&size)) != KEYC_NONE) {
+		log_debug("key code: %d", key);
+
+		if (key == client_cmd_prefix) {
+			if ((key = local_key(NULL)) == KEYC_NONE) {
+				/* XXX sux */
+				buffer_reverse_remove(cctx->loc_in, size);
+				break;
+			}
+			n = client_cmd_dispatch(key, cctx, error);
+			break;
+		}
+
+		input_store8(b, '\e');
+		input_store16(b, (uint16_t) key /*XXX*/);
+	}
+
+	log_debug("transmitting %zu bytes of input", BUFFER_USED(b));
+	if (BUFFER_USED(b) == 0) {
+		buffer_destroy(b);
+		return (n);
+	}
+	client_write_server(cctx, MSG_INPUT, BUFFER_OUT(b), BUFFER_USED(b));
+	buffer_destroy(b);
+	return (n);
+}
+
diff --git a/command.c b/command.c
deleted file mode 100644
index d07bd35f..00000000
--- a/command.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/* $Id: command.c,v 1.7 2007-09-22 11:50:33 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 "tmux.h"
-
-int	cmd_prefix = META;
-
-int	cmd_fn_select(struct buffer *, int);
-int	cmd_fn_detach(struct buffer *, int);
-int	cmd_fn_msg(struct buffer *, int);
-
-struct cmd {
-	int	key;
-	int	(*fn)(struct buffer *, int);
-	int	arg;
-};
-
-struct cmd cmd_table[] = {
-	{ '0', cmd_fn_select, 0 },
-	{ '1', cmd_fn_select, 1 },
-	{ '2', cmd_fn_select, 2 },
-	{ '3', cmd_fn_select, 3 },
-	{ '4', cmd_fn_select, 4 },
-	{ '5', cmd_fn_select, 5 },
-	{ '6', cmd_fn_select, 6 },
-	{ '7', cmd_fn_select, 7 },
-	{ '8', cmd_fn_select, 8 },
-	{ '9', cmd_fn_select, 9 },
-	{ 'C', cmd_fn_msg, MSG_CREATE },
-	{ 'c', cmd_fn_msg, MSG_CREATE },
-	{ 'D', cmd_fn_detach, 0 },
-	{ 'd', cmd_fn_detach, 0 },
-	{ 'N', cmd_fn_msg, MSG_NEXT },
-	{ 'n', cmd_fn_msg, MSG_NEXT },
-	{ 'P', cmd_fn_msg, MSG_PREVIOUS },
-	{ 'p', cmd_fn_msg, MSG_PREVIOUS },
-	{ 'R', cmd_fn_msg, MSG_REFRESH },
-	{ 'r', cmd_fn_msg, MSG_REFRESH },
-	{ 'T', cmd_fn_msg, MSG_RENAME },
-	{ 't', cmd_fn_msg, MSG_RENAME },
-	{ 'L', cmd_fn_msg, MSG_LAST },
-	{ 'l', cmd_fn_msg, MSG_LAST },
-	{ 'W', cmd_fn_msg, MSG_WINDOWLIST },
-	{ 'w', cmd_fn_msg, MSG_WINDOWLIST }
-};
-
-/* Dispatch to a command. */
-int
-cmd_execute(int key, struct buffer *srv_out)
-{
-	struct cmd	*cmd;
-	u_int		 i;
-
-	for (i = 0; i < (sizeof cmd_table / sizeof cmd_table[0]); i++) {
-		cmd = cmd_table + i;
-		if (cmd->key == key)
-			return (cmd->fn(srv_out, cmd->arg));
-	}
-	return (0);
-}
-
-/* Handle generic command. */
-int
-cmd_fn_msg(struct buffer *srv_out, int type)
-{
- 	struct hdr	hdr;
-
-	hdr.type = type;
-	hdr.size = 0;
-	buffer_write(srv_out, &hdr, sizeof hdr);
-
- 	return (0);
-}
-
-/* Handle select command. */
-int
-cmd_fn_select(struct buffer *srv_out, int arg)
-{
- 	struct hdr		hdr;
-	struct select_data	data;
-
-	hdr.type = MSG_SELECT;
-	hdr.size = sizeof data;
-	buffer_write(srv_out, &hdr, sizeof hdr);
-	data.idx = arg;
-	buffer_write(srv_out, &data, sizeof data);
-
-	return (0);
-}
-
-/* Handle detach command. */
-int
-cmd_fn_detach(unused struct buffer *srv_out, unused int arg)
-{
-	return (-1);
-}
diff --git a/op-list.c b/op-list.c
new file mode 100644
index 00000000..e2f78efe
--- /dev/null
+++ b/op-list.c
@@ -0,0 +1,126 @@
+/* $Id: op-list.c,v 1.1 2007-09-26 13:43:15 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.
+ */
+
+/* List sessions or windows. */
+void
+op_list(char *name)
+{
+	struct sessions_data	 sd;
+	struct windows_data	 wd;
+	struct pollfd	 	 pfd;
+	struct hdr		 hdr;
+
+	/* Send query data. */
+	if (*name == '\0') {
+		hdr.type = MSG_SESSIONS;
+		hdr.size = sizeof sd;
+		buffer_write(server_out, &hdr, sizeof hdr);
+		buffer_write(server_out, &sd, hdr.size);
+	} else {
+		hdr.type = MSG_WINDOWS;
+		hdr.size = sizeof wd;
+		buffer_write(server_out, &hdr, sizeof hdr);
+		strlcpy(wd.name, name, sizeof wd.name);
+		buffer_write(server_out, &wd, hdr.size);
+	}
+
+	/* Main loop. */
+	for (;;) {
+		/* Set up pollfd. */
+		pfd.fd = server_fd;
+		pfd.events = POLLIN;
+		if (BUFFER_USED(server_out) > 0)
+			pfd.events |= POLLOUT;
+
+		/* Do the poll. */
+		if (poll(&pfd, 1, INFTIM) == -1) {
+			if (errno == EAGAIN || errno == EINTR)
+				continue;
+			err(1, "poll");
+		}
+
+		/* Read/write from sockets. */
+		if (buffer_poll(&pfd, server_in, server_out) != 0)
+			errx(1, "lost server"); 
+
+		/* Process data. */
+		process_list(name);
+	}
+}
+
+void
+op_list_process(const char *name)
+{
+	struct sessions_data	 sd;
+	struct sessions_entry	 se;
+	struct windows_data	 wd;
+	struct windows_entry	 we;
+	struct hdr		 hdr;
+	char		        *tim;
+	
+	for (;;) {
+		if (BUFFER_USED(server_in) < sizeof hdr)
+			break;
+		memcpy(&hdr, BUFFER_OUT(server_in), sizeof hdr);
+		if (BUFFER_USED(server_in) < (sizeof hdr) + hdr.size)
+			break;
+		buffer_remove(server_in, sizeof hdr);
+		
+		switch (hdr.type) {
+		case MSG_SESSIONS:
+			if (hdr.size < sizeof sd)
+				errx(1, "bad MSG_SESSIONS size");
+			buffer_read(server_in, &sd, sizeof sd);
+			hdr.size -= sizeof sd; 
+			if (sd.sessions == 0 && hdr.size == 0)
+				exit(0);
+			if (hdr.size < sd.sessions * sizeof se)
+				errx(1, "bad MSG_SESSIONS size");
+			while (sd.sessions-- > 0) {
+				buffer_read(server_in, &se, sizeof se);
+				tim = ctime(&se.tim);
+				*strchr(tim, '\n') = '\0';
+				printf("%s: %u windows (created %s)\n",
+				    se.name, se.windows, tim);
+			}
+			exit(0);
+		case MSG_WINDOWS:
+			if (hdr.size < sizeof wd)
+				errx(1, "bad MSG_WINDOWS size");
+			buffer_read(server_in, &wd, sizeof wd);
+			hdr.size -= sizeof wd; 
+			if (wd.windows == 0 && hdr.size == 0)
+				errx(1, "session not found: %s", name);
+			if (hdr.size < wd.windows * sizeof we)
+				errx(1, "bad MSG_WINDOWS size");
+			while (wd.windows-- > 0) {
+				buffer_read(server_in, &we, sizeof we);
+				if (*we.title != '\0') {
+					printf("%u: %s \"%s\" (%s)\n",
+					    we.idx, we.name, we.title, we.tty); 
+				} else {
+					printf("%u: %s (%s)\n",
+					    we.idx, we.name, we.tty);
+				}
+			}
+			exit(0);
+		default:
+			fatalx("unexpected message");
+		}
+	}
+}
diff --git a/op.c b/op.c
new file mode 100644
index 00000000..393ca69f
--- /dev/null
+++ b/op.c
@@ -0,0 +1,64 @@
+/* $Id: op.c,v 1.1 2007-09-26 13:43:15 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 <string.h>
+
+#include "tmux.h"
+
+int
+op_new(char *path, int argc, unused char **argv)
+{
+	struct new_data	 	data;	
+	struct client_ctx	cctx;
+
+	if (argc != 0) /* XXX -n */
+		return (usage("new"));
+
+	if (client_init(path, &cctx, 1) != 0)
+		return (1);
+
+	strlcpy(data.name, "XXX"/*XXX*/, sizeof data.name);
+	data.sx = cctx.ws.ws_col;
+	data.sy = cctx.ws.ws_row;
+	client_write_server(&cctx, MSG_NEW, &data, sizeof data);
+
+	return (client_main(&cctx));
+}
+
+int
+op_attach(char *path, int argc, unused char **argv)
+{
+	struct attach_data	data;
+	struct client_ctx	cctx;
+
+	if (argc != 0) /* XXX -n */
+		return (usage("attach"));
+
+	if (client_init(path, &cctx, 1) != 0)
+		return (1);
+
+	strlcpy(data.name, "XXX"/*XXX*/, sizeof data.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));
+}
+
diff --git a/server-msg.c b/server-msg.c
index 8bf5af2a..1c0de755 100644
--- a/server-msg.c
+++ b/server-msg.c
@@ -1,4 +1,4 @@
-/* $Id: server-msg.c,v 1.1 2007-09-26 10:35:24 nicm Exp $ */
+/* $Id: server-msg.c,v 1.2 2007-09-26 13:43:15 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -24,25 +24,25 @@
 
 #include "tmux.h"
 
-void	server_msg_fn_attach(struct client *, struct hdr *);
-void	server_msg_fn_create(struct client *, struct hdr *);
-void	server_msg_fn_input(struct client *, struct hdr *);
-void	server_msg_fn_last(struct client *, struct hdr *);
-void	server_msg_fn_new(struct client *, struct hdr *);
-void	server_msg_fn_next(struct client *, struct hdr *);
-void	server_msg_fn_previous(struct client *, struct hdr *);
-void	server_msg_fn_refresh(struct client *, struct hdr *);
-void	server_msg_fn_rename(struct client *, struct hdr *);
-void	server_msg_fn_select(struct client *, struct hdr *);
-void	server_msg_fn_sessions(struct client *, struct hdr *);
-void	server_msg_fn_size(struct client *, struct hdr *);
-void	server_msg_fn_windowlist(struct client *, struct hdr *);
-void	server_msg_fn_windows(struct client *, struct hdr *);
+int	server_msg_fn_attach(struct hdr *, struct client *);
+int	server_msg_fn_create(struct hdr *, struct client *);
+int	server_msg_fn_input(struct hdr *, struct client *);
+int	server_msg_fn_last(struct hdr *, struct client *);
+int	server_msg_fn_new(struct hdr *, struct client *);
+int	server_msg_fn_next(struct hdr *, struct client *);
+int	server_msg_fn_previous(struct hdr *, struct client *);
+int	server_msg_fn_refresh(struct hdr *, struct client *);
+int	server_msg_fn_rename(struct hdr *, struct client *);
+int	server_msg_fn_select(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_windowlist(struct hdr *, struct client *);
+int	server_msg_fn_windows(struct hdr *, struct client *);
 
 struct server_msg {
 	enum hdrtype	type;
 	
-	void	        (*fn)(struct client *, struct hdr *);
+	int	        (*fn)(struct hdr *, struct client *);
 };
 struct server_msg server_msg_table[] = {
 	{ MSG_ATTACH, server_msg_fn_attach },
@@ -62,41 +62,45 @@ struct server_msg server_msg_table[] = {
 };
 #define NSERVERMSG (sizeof server_msg_table / sizeof server_msg_table[0])
 
-void
+int
 server_msg_dispatch(struct client *c)
 {
 	struct hdr		 hdr;
 	struct server_msg	*msg;
 	u_int		 	 i;
+	int			 n;
 
-	if (BUFFER_USED(c->in) < sizeof hdr)
-		return;
-	memcpy(&hdr, BUFFER_OUT(c->in), sizeof hdr);
-	if (BUFFER_USED(c->in) < (sizeof hdr) + hdr.size)
-		return;
-	buffer_remove(c->in, sizeof hdr);
-
-	for (i = 0; i < NSERVERMSG; i++) {
-		msg = server_msg_table + i;
-		if (msg->type == hdr.type) {
-			msg->fn(c, &hdr);
-			return;
-		}
+	for (;;) {
+		if (BUFFER_USED(c->in) < sizeof hdr)
+			return (0);
+		memcpy(&hdr, BUFFER_OUT(c->in), sizeof hdr);
+		if (BUFFER_USED(c->in) < (sizeof hdr) + hdr.size)
+			return (0);
+		buffer_remove(c->in, sizeof hdr);
+		
+		for (i = 0; i < NSERVERMSG; i++) {
+			msg = server_msg_table + i;
+			if (msg->type == hdr.type) {
+				if ((n = msg->fn(&hdr, c)) != 0)
+					return (n);
+				break;
+			}
+		}	
+		if (i == NSERVERMSG)
+			fatalx("unexpected message");
 	}
-
-	fatalx("unexpected message");
 }
 
 /* New message from client. */
-void
-server_msg_fn_new(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_new(struct hdr *hdr, struct client *c)
 {
 	struct new_data	 data;
 	const char      *shell;
 	char		*cmd, *msg;
 	
 	if (c->session != NULL)
-		return;
+		return (0);
 	if (hdr->size != sizeof data)
 		fatalx("bad MSG_NEW size");
 	buffer_read(c->in, &data, hdr->size);
@@ -110,9 +114,9 @@ server_msg_fn_new(struct client *c, struct hdr *hdr)
 
 	if (*data.name != '\0' && session_find(data.name) != NULL) {
 		xasprintf(&msg, "duplicate session: %s", data.name);
-		write_client(c, MSG_READY, msg, strlen(msg));
+		write_client(c, MSG_ERROR, msg, strlen(msg));
 		xfree(msg);
-		return;
+		return (0);
 	}
 
 	shell = getenv("SHELL");
@@ -124,19 +128,20 @@ server_msg_fn_new(struct client *c, struct hdr *hdr)
 		fatalx("session_create failed");
 	xfree(cmd);
 	
-	write_client(c, MSG_READY, NULL, 0);
 	draw_client(c, 0, c->sy - 1);
+
+	return (0);
 }
 
 /* Attach message from client. */
-void
-server_msg_fn_attach(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_attach(struct hdr *hdr, struct client *c)
 {
 	struct attach_data	 data;
 	char			*msg;
 	
 	if (c->session != NULL)
-		return;
+		return (0);
 	if (hdr->size != sizeof data)
 		fatalx("bad MSG_ATTACH size");
 	buffer_read(c->in, &data, hdr->size);
@@ -152,24 +157,25 @@ server_msg_fn_attach(struct client *c, struct hdr *hdr)
 		c->session = session_find(data.name);
 	if (c->session == NULL) {
 		xasprintf(&msg, "session not found: %s", data.name);
-		write_client(c, MSG_READY, msg, strlen(msg));
+		write_client(c, MSG_ERROR, msg, strlen(msg));
 		xfree(msg);
-		return;
+		return (0);
 	}
 
-	write_client(c, MSG_READY, NULL, 0);
 	draw_client(c, 0, c->sy - 1);
+
+	return (0);
 }
 
 /* Create message from client. */
-void
-server_msg_fn_create(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_create(struct hdr *hdr, struct client *c)
 {
 	const char	*shell;
 	char		*cmd;
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_CREATE size");
 
@@ -182,14 +188,16 @@ server_msg_fn_create(struct client *c, struct hdr *hdr)
 	xfree(cmd);
 
 	draw_client(c, 0, c->sy - 1);
+
+	return (0);
 }
 
 /* Next message from client. */
-void
-server_msg_fn_next(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_next(struct hdr *hdr, struct client *c)
 {
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_NEXT size");
 
@@ -197,14 +205,16 @@ server_msg_fn_next(struct client *c, struct hdr *hdr)
 		changed_window(c);
 	else
 		write_message(c, "No next window"); 
+
+	return (0);
 }
 
 /* Previous message from client. */
-void
-server_msg_fn_previous(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_previous(struct hdr *hdr, struct client *c)
 {
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_PREVIOUS size");
 
@@ -212,16 +222,18 @@ server_msg_fn_previous(struct client *c, struct hdr *hdr)
 		changed_window(c);
 	else
 		write_message(c, "No previous window"); 
+
+	return (0);
 }
 
 /* Size message from client. */
-void
-server_msg_fn_size(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_size(struct hdr *hdr, struct client *c)
 {
 	struct size_data	data;
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != sizeof data)
 		fatalx("bad MSG_SIZE size");
 	buffer_read(c->in, &data, hdr->size);
@@ -235,55 +247,63 @@ server_msg_fn_size(struct client *c, struct hdr *hdr)
 
 	if (window_resize(c->session->window, c->sx, c->sy) != 0)
 		draw_client(c, 0, c->sy - 1);
+
+	return (0);
 }
 
 /* Input message from client. */
-void
-server_msg_fn_input(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_input(struct hdr *hdr, struct client *c)
 {
 	if (c->session == NULL)
-		return;
+		return (0);
 	
 	window_input(c->session->window, c->in, hdr->size);
+
+	return (0);
 }
 
 /* Refresh message from client. */
-void
-server_msg_fn_refresh(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_refresh(struct hdr *hdr, struct client *c)
 {
 	struct refresh_data	data;
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0 && hdr->size != sizeof data)
 		fatalx("bad MSG_REFRESH size");
 
 	draw_client(c, 0, c->sy - 1);
+
+	return (0);
 }
 
 /* Select message from client. */
-void
-server_msg_fn_select(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_select(struct hdr *hdr, struct client *c)
 {
 	struct select_data	data;
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != sizeof data)
 		fatalx("bad MSG_SELECT size");
 	buffer_read(c->in, &data, hdr->size);
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (session_select(c->session, data.idx) == 0)
 		changed_window(c);
 	else
 		write_message(c, "Window %u not present", data.idx); 
+
+	return (0);
 }
 
 /* Sessions message from client. */
-void
-server_msg_fn_sessions(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_sessions(struct hdr *hdr, struct client *c)
 {
 	struct sessions_data	 data;
 	struct sessions_entry	 entry;
@@ -315,11 +335,13 @@ server_msg_fn_sessions(struct client *c, struct hdr *hdr)
 		}
 		buffer_write(c->out, &entry, sizeof entry);
 	}
+
+	return (0);
 }
 
 /* Windows message from client. */
-void
-server_msg_fn_windows(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_windows(struct hdr *hdr, struct client *c)
 {
 	struct windows_data	 data;
 	struct windows_entry	 entry;
@@ -335,7 +357,7 @@ server_msg_fn_windows(struct client *c, struct hdr *hdr)
 	if (s == NULL) {
 		data.windows = 0;
 		write_client(c, MSG_WINDOWS, &data, sizeof data);
-		return;
+		return (0);
 	}
 
 	data.windows = 0;
@@ -357,14 +379,16 @@ server_msg_fn_windows(struct client *c, struct hdr *hdr)
 			*entry.tty = '\0';
 		buffer_write(c->out, &entry, sizeof entry);
 	}
+
+	return (0);
 }
 
 /* Rename message from client. */
-void
-server_msg_fn_rename(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_rename(struct hdr *hdr, struct client *c)
 {
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_RENAME size");
 
@@ -372,11 +396,11 @@ server_msg_fn_rename(struct client *c, struct hdr *hdr)
 }
 
 /* Last window message from client */
-void
-server_msg_fn_last(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_last(struct hdr *hdr, struct client *c)
 {
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_LAST size");
 
@@ -384,11 +408,13 @@ server_msg_fn_last(struct client *c, struct hdr *hdr)
 		changed_window(c);
 	else
 		write_message(c, "No last window"); 
+
+	return (0);
 }
 
 /* Window list message from client */
-void
-server_msg_fn_windowlist(struct client *c, struct hdr *hdr)
+int
+server_msg_fn_windowlist(struct hdr *hdr, struct client *c)
 {
 	struct window	*w;
 	char 		*buf;
@@ -396,7 +422,7 @@ server_msg_fn_windowlist(struct client *c, struct hdr *hdr)
 	u_int 		 i;
 
 	if (c->session == NULL)
-		return;
+		return (0);
 	if (hdr->size != 0)
 		fatalx("bad MSG_WINDOWLIST size");
 
@@ -417,4 +443,6 @@ server_msg_fn_windowlist(struct client *c, struct hdr *hdr)
 
 	write_message(c, "%s", buf);
 	xfree(buf);
+
+	return (0);
 }
diff --git a/server.c b/server.c
index 21a35f4f..3c12543d 100644
--- a/server.c
+++ b/server.c
@@ -1,4 +1,4 @@
-/* $Id: server.c,v 1.12 2007-09-26 10:35:24 nicm Exp $ */
+/* $Id: server.c,v 1.13 2007-09-26 13:43:15 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -24,6 +24,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <paths.h>
 #include <poll.h>
 #include <signal.h>
 #include <stdio.h>
@@ -43,7 +44,7 @@
 /* Client list. */
 struct clients	 clients;
 
-int		 server_main(int);
+int		 server_main(char *, int);
 void		 fill_windows(struct pollfd **);
 void		 handle_windows(struct pollfd **);
 void		 fill_clients(struct pollfd **);
@@ -52,21 +53,18 @@ struct client	*accept_client(int);
 void		 lost_client(struct client *);
 void	 	 lost_window(struct window *);
 
-/* Fork and start server process. */
 int
-server_start(void)
+server_start(char *path)
 {
-	mode_t			mode;
-	int		   	fd;
 	struct sockaddr_un	sa;
 	size_t			sz;
 	pid_t			pid;
-	FILE		       *f;
-	char		       *path;
+	mode_t			mode;
+	int		   	fd;
 
-	/* Fork the server process. */
 	switch (pid = fork()) {
 	case -1:
+		log_warn("fork");
 		return (-1);
 	case 0:
 		break;
@@ -74,20 +72,15 @@ server_start(void)
 		return (0);
 	}
 
-	/* Start logging to file. */
-	if (debug_level > 0) {
-		xasprintf(&path,
-		    "%s-server-%ld.log", __progname, (long) getpid());
-		f = fopen(path, "w");
-		log_open(f, LOG_DAEMON, debug_level);
-		xfree(path);
-	}
+	logfile("server");
+	setproctitle("server (%s)", path);
+
 	log_debug("server started, pid %ld", (long) getpid());
 
 	/* Create the socket. */
 	memset(&sa, 0, sizeof sa);
 	sa.sun_family = AF_UNIX;
-	sz = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
+	sz = strlcpy(sa.sun_path, path, sizeof sa.sun_path);
 	if (sz >= sizeof sa.sun_path) {
 		errno = ENAMETOOLONG;
 		fatal("socket failed");
@@ -113,32 +106,17 @@ server_start(void)
 		fatal("daemon failed");
 	log_debug("server daemonised, pid now %ld", (long) getpid());
 
-	setproctitle("server (%s)", socket_path);
-	exit(server_main(fd));
+	exit(server_main(path, fd));
 }
 
 /* Main server loop. */
 int
-server_main(int srv_fd)
+server_main(char *srv_path, int srv_fd)
 {
 	struct pollfd  		*pfds, *pfd;
 	int			 nfds, mode;
-	struct sigaction	 act;
 
-	sigemptyset(&act.sa_mask);
-	act.sa_flags = SA_RESTART;
-
-	act.sa_handler = SIG_IGN;
-	if (sigaction(SIGPIPE, &act, NULL) != 0)
-		fatal("sigaction failed");
-	if (sigaction(SIGUSR1, &act, NULL) != 0)
-		fatal("sigaction failed");
-	if (sigaction(SIGUSR2, &act, NULL) != 0)
-		fatal("sigaction failed");
-	if (sigaction(SIGINT, &act, NULL) != 0)
-		fatal("sigaction failed");
-	if (sigaction(SIGQUIT, &act, NULL) != 0)
-		fatal("sigaction failed");
+	siginit();
 
 	ARRAY_INIT(&windows);
 	ARRAY_INIT(&clients);
@@ -192,7 +170,7 @@ server_main(int srv_fd)
 	}
 
 	close(srv_fd);
-	unlink(socket_path);
+	unlink(srv_path);
 
 	return (0);
 }
diff --git a/tmux.c b/tmux.c
index 881e7900..6ae982eb 100644
--- a/tmux.c
+++ b/tmux.c
@@ -1,4 +1,4 @@
-/* $Id: tmux.c,v 1.8 2007-09-21 18:00:58 nicm Exp $ */
+/* $Id: tmux.c,v 1.9 2007-09-26 13:43:15 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -17,17 +17,9 @@
  */
 
 #include <sys/types.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/un.h>
 #include <sys/wait.h>
 
-#include <err.h>
-#include <errno.h>
-#include <fcntl.h>
 #include <paths.h>
-#include <getopt.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
@@ -40,44 +32,46 @@
 const char	*malloc_options = "AFGJPX";
 #endif
 
-void		 op_new(char *, struct winsize *);
-void		 op_attach(char *, struct winsize *);
-int	 	 connect_server(void);
-int		 process_server(char **);
-int		 process_local(void);
-void		 sighandler(int);
-__dead void	 usage(void);
-__dead void	 main_list(char *);
-void		 process_list(const char *);
-
-/* SIGWINCH received flag. */
 volatile sig_atomic_t sigwinch;
-
-/* SIGTERM received flag. */
 volatile sig_atomic_t sigterm;
-
-/* Debug output level. */
 int		 debug_level;
 
-/* Path to server socket. */
-char		 socket_path[MAXPATHLEN];
+void		 sighandler(int);
 
-/* Server socket and buffers. */
-int		 server_fd = -1;
-struct buffer	*server_in;
-struct buffer	*server_out;
+struct op {
+	const char     *cmd;	
+	int		(*fn)(char *, int, char **);
+};
+struct op op_table[] = {
+//	{ "list", op_list },
+	{ "new", op_new },
+	{ "attach", op_attach }
+};
+#define NOP (sizeof op_table / sizeof op_table[0])
 
-/* Local socket and buffers. */
-int		 local_fd = -1;
-struct buffer	*local_in;
-struct buffer	*local_out;
-
-__dead void
-usage(void)
+int
+usage(const char *s)
 {
-	fprintf(stderr,
-	    "usage: %s [-v] [-n name] [-s path] command\n", __progname);
-        exit(1);
+	if (s == NULL)
+		s = "command ...";
+	fprintf(stderr, "usage: %s [-v] [-s path] %s\n", __progname, s);
+	return (1);
+}
+
+void
+logfile(const char *name)
+{
+	FILE	*f;
+	char	*path;
+
+	log_close();
+	if (debug_level > 0) {
+		xasprintf(
+		    &path, "%s-%s-%ld.log", __progname, name, (long) getpid());
+		f = fopen(path, "w");
+		log_open(f, LOG_DAEMON, debug_level);
+		xfree(path);
+	}
 }
 
 void
@@ -96,29 +90,78 @@ sighandler(int sig)
 	}
 }
 
+void
+siginit(void)
+{
+	struct sigaction	 act;
+
+	memset(&act, 0, sizeof act);
+	sigemptyset(&act.sa_mask);
+	act.sa_flags = SA_RESTART;
+
+	act.sa_handler = SIG_IGN;
+	if (sigaction(SIGPIPE, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGUSR1, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGUSR2, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGINT, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGTSTP, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGQUIT, &act, NULL) != 0)
+		fatal("sigaction failed");
+
+	act.sa_handler = sighandler;
+	if (sigaction(SIGWINCH, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGTERM, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGCHLD, &act, NULL) != 0)
+		fatal("sigaction failed");
+}
+
+void
+sigreset(void)
+{
+	struct sigaction act;
+	
+	memset(&act, 0, sizeof act);
+	sigemptyset(&act.sa_mask);
+	
+	act.sa_handler = SIG_DFL;
+	if (sigaction(SIGPIPE, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGUSR1, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGUSR2, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGINT, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGTSTP, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGQUIT, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGWINCH, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGTERM, &act, NULL) != 0)
+		fatal("sigaction failed");
+	if (sigaction(SIGCHLD, &act, NULL) != 0)
+		fatal("sigaction failed");
+}
+
 int
 main(int argc, char **argv)
 {
-	int	 		 opt, mode, n;
-	char			*path, *error, name[MAXNAMELEN];
-	FILE			*f;
-	enum op			 op;
-	struct pollfd		 pfds[2];
-	struct hdr		 hdr;
-	struct size_data	 sd;
-	struct winsize	 	 ws;
-	struct sigaction	 act;
-	struct stat		 sb;
+	struct op		*op;
+	char			*path;
+	int	 		 opt;
+	u_int			 i;
 
-	*name = '\0';
 	path = NULL;
-
-        while ((opt = getopt(argc, argv, "n:s:v?")) != EOF) {
+        while ((opt = getopt(argc, argv, "s:v?")) != EOF) {
                 switch (opt) {
-		case 'n':
-			if (strlcpy(name, optarg, sizeof name) >= sizeof name)
-				errx(1, "name too long");
-			break;
 		case 's':
 			path = xstrdup(optarg);
 			break;
@@ -127,449 +170,24 @@ main(int argc, char **argv)
 			break;
                 case '?':
                 default:
-                        usage();
+                        exit(usage(NULL));
                 }
         }
 	argc -= optind;
 	argv += optind;
-	if (argc != 1)
-		usage();
+	if (argc == 0)
+		exit(usage(NULL));
 
-	/* Determine command. */
-	if (strncmp(argv[0], "list", strlen(argv[0])) == 0)
-		op = OP_LIST;
-	else if (strncmp(argv[0], "new", strlen(argv[0])) == 0)
-		op = OP_NEW;
-	else if (strncmp(argv[0], "attach", strlen(argv[0])) == 0)
-		op = OP_ATTACH;
-	else
-		usage();
+	log_open(stderr, LOG_USER, debug_level);
 
-	/* Sort out socket path. */
-	if (path == NULL) {
-		xasprintf(&path,
-		    "%s/%s-%lu", _PATH_TMP, __progname, (u_long) getuid());
-	}
-	if (realpath(path, socket_path) == NULL)
-		err(1, "realpath");
-	xfree(path);
-
-	/* Set up signal handlers. */
-	memset(&act, 0, sizeof act);
-	sigemptyset(&act.sa_mask);
-	act.sa_flags = SA_RESTART;
-
-	act.sa_handler = SIG_IGN;
-	if (sigaction(SIGPIPE, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGUSR1, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGUSR2, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGINT, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGTSTP, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGQUIT, &act, NULL) != 0)
-		err(1, "sigaction");
-
-	act.sa_handler = sighandler;
-	if (sigaction(SIGWINCH, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGTERM, &act, NULL) != 0)
-		err(1, "sigaction");
-	if (sigaction(SIGCHLD, &act, NULL) != 0)
-		err(1, "sigaction");
-
-	/* Start server if necessary. */
-	n = 0;
-restart:
-	if (stat(socket_path, &sb) != 0) {
-		if (errno != ENOENT)
-			err(1, "%s", socket_path);
-		else if (op != OP_LIST) {
-			if (server_start() != 0)
-				errx(1, "couldn't start server");	
-			sleep(1); /* XXX this sucks */
-		}
-	} else {
-		if (!S_ISSOCK(sb.st_mode))
-			errx(1, "%s: not a socket", socket_path);
-	}
-
-	/* Connect to server. */
-	if ((server_fd = connect_server()) == -1) {
-		if (errno == ECONNREFUSED && n++ < 5) {
-			unlink(socket_path);
-			goto restart;
-		}
-		errx(1, "couldn't find server");
-	}
-	if ((mode = fcntl(server_fd, F_GETFL)) == -1)
-		err(1, "fcntl");
-	if (fcntl(server_fd, F_SETFL, mode|O_NONBLOCK) == -1)
-		err(1, "fcntl");
-	server_in = buffer_create(BUFSIZ);
-	server_out = buffer_create(BUFSIZ);
-
-	/* Skip to list function if listing. */
-	if (op == OP_LIST)
-		main_list(name);
-
-	/* Check stdin/stdout. */
-	if (!isatty(STDIN_FILENO))
-		errx(1, "stdin is not a tty");
-	if (!isatty(STDOUT_FILENO))
-		errx(1, "stdout is not a tty");
-
-	/* Find window size. */
-	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
-		err(1, "ioctl(TIOCGWINSZ)");
-
-	/* Send initial data. */
-	switch (op) {
-	case OP_NEW:
-		op_new(name, &ws);
-		break;
-	case OP_ATTACH:
-		op_attach(name, &ws);
-		break;
-	default:
-		fatalx("unknown op");
-	}
-
-	/* Start logging to file. */
-	if (debug_level > 0) {
-		xasprintf(&path,
-		    "%s-client-%ld.log", __progname, (long) getpid());
-		f = fopen(path, "w");
-		log_open(f, LOG_USER, debug_level);
-		xfree(path);
-	}
-	setproctitle("client (%s)", name);
-
-	/* Main loop. */
-	n = 0;
-	while (!sigterm) {
-		/* Handle SIGWINCH if necessary. */
-		if (local_fd != -1 && sigwinch) {
-			if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
-				fatal("ioctl failed");
-
-			hdr.type = MSG_SIZE;
-			hdr.size = sizeof sd;
-			buffer_write(server_out, &hdr, sizeof hdr);
-			sd.sx = ws.ws_col;
-			sd.sy = ws.ws_row;
-			buffer_write(server_out, &sd, hdr.size);
-
-			sigwinch = 0;
-		}
-
-		/* Set up pollfds. */
-		pfds[0].fd = server_fd;
-		pfds[0].events = POLLIN;
-		if (BUFFER_USED(server_out) > 0)
-			pfds[0].events |= POLLOUT;
-		pfds[1].fd = local_fd;
-		pfds[1].events = POLLIN;
-		if (local_fd != -1 && BUFFER_USED(local_out) > 0)
-			pfds[1].events |= POLLOUT;
-	
-		/* Do the poll. */
-		if (poll(pfds, 2, INFTIM) == -1) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			fatal("poll failed");
-		}
-
-		/* Read/write from sockets. */
-		if (buffer_poll(&pfds[0], server_in, server_out) != 0)
-			goto server_dead;
-		if (local_fd != -1 && 
-		    buffer_poll(&pfds[1], local_in, local_out) != 0)
-			fatalx("lost local socket");
-
-		/* Output flushed; pause if requested. */
-		if (n)
-			usleep(750000);
-
-		/* Process any data. */
-		if ((n = process_server(&error)) == -1)
-			break;
-		if (process_local() == -1)
-			break;
-	}
-
-	if (local_fd != -1)
-		local_done();
-
-	if (error != NULL)
-		errx(1, "%s", error);
-
-	if (!sigterm)
-		printf("[detached]\n");
-	else
-		printf("[terminated]\n");
-	exit(0);
-
-server_dead:
-	if (local_fd != -1)
-		local_done();
-
-	printf("[lost server]\n");
-	exit(1);
-}
-
-/* New command. */
-void
-op_new(char *name, struct winsize *ws)
-{
-	struct new_data	data;
-	struct hdr	hdr;
-
-	hdr.type = MSG_NEW;
-	hdr.size = sizeof data;
-	buffer_write(server_out, &hdr, sizeof hdr);
-
-	strlcpy(data.name, name, sizeof data.name);
-	data.sx = ws->ws_col;
-	data.sy = ws->ws_row;
-	buffer_write(server_out, &data, hdr.size);
-}
-
-/* Attach command. */
-void
-op_attach(char *name, struct winsize *ws)
-{
-	struct attach_data	data;
-	struct hdr		hdr;
-
-	hdr.type = MSG_ATTACH;
-	hdr.size = sizeof data;
-	buffer_write(server_out, &hdr, sizeof hdr);
-
-	strlcpy(data.name, name, sizeof data.name);
-	data.sx = ws->ws_col;
-	data.sy = ws->ws_row;
-	buffer_write(server_out, &data, hdr.size);
-}
-
-/* Connect to server socket from PID. */
-int
-connect_server(void)
-{
-	int			fd;
-	struct sockaddr_un	sa;
-	size_t			sz;
-
-	memset(&sa, 0, sizeof sa);
-	sa.sun_family = AF_UNIX;
-	sz = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
-	if (sz >= sizeof sa.sun_path) {
-		errno = ENAMETOOLONG;
-		return (-1);
-	}
-
-	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
-		return (-1);
-	if (connect(fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) == -1)
-		return (-1);
-	return (fd);
-}
-
-/* Handle data from server. */
-int
-process_server(char **error)
-{
-	struct hdr	hdr;
-
-	*error = NULL;
-	for (;;) {
-		if (BUFFER_USED(server_in) < sizeof hdr)
-			break;
-		memcpy(&hdr, BUFFER_OUT(server_in), sizeof hdr);
-		if (BUFFER_USED(server_in) < (sizeof hdr) + hdr.size)
-			break;
-		buffer_remove(server_in, sizeof hdr);
-
-		switch (hdr.type) {
-		case MSG_READY:
-			if (hdr.size != 0) {
-				xasprintf(error, "%.*s",
-				    (int) hdr.size, BUFFER_OUT(server_in));
-				return (-1);
-			}
-			local_fd = local_init(&local_in, &local_out);
-			break;
-		case MSG_OUTPUT:
-			local_output(server_in, hdr.size);
-			break;
-		case MSG_PAUSE:
-			if (hdr.size != 0)
-				fatalx("bad MSG_PAUSE size");
-			return (1);
-		case MSG_EXIT:
-			return (-1);
-		default:
-			fatalx("unexpected message");
+	for (i = 0; i < NOP; i++) {
+		op = op_table + i;
+		if (strncmp(argv[0], op->cmd, strlen(op->cmd)) == 0) {
+			argc--;
+			argv++;
+			exit(op->fn(path, argc, argv));
 		}
 	}
 
-	return (0);
-}
-
-/* Handle data from local terminal. */
-int
-process_local(void)
-{
-	struct buffer	*b;
-	struct hdr	 hdr;
-	size_t		 size;
-	int		 n, key;
-
-	if (local_fd == -1)
-		return (0);
-
-	n = 0;
-	b = buffer_create(BUFSIZ);
-
-	while ((key = local_key(&size)) != KEYC_NONE) {
-		log_debug("key code: %d", key);
-
-		if (key == cmd_prefix) {
-			if ((key = local_key(NULL)) == KEYC_NONE) {
-				buffer_reverse_remove(local_in, size);
-				break;
-			}
-			n = cmd_execute(key, server_out);
-			break;
-		}
-
-		input_store8(b, '\e');
-		input_store16(b, (uint16_t) key);
-	}
-
-	if (BUFFER_USED(b) == 0) {
-		buffer_destroy(b);
-		return (n);
-	}
-	log_debug("transmitting %zu bytes of input", BUFFER_USED(b));
-
-	hdr.type = MSG_INPUT;
-	hdr.size = BUFFER_USED(b);
-	buffer_write(server_out, &hdr, sizeof hdr);
-	buffer_write(server_out, BUFFER_OUT(b), BUFFER_USED(b));
-
-	buffer_destroy(b);
-	return (n);
-}
-
-/* List sessions or windows. */
-__dead void
-main_list(char *name)
-{
-	struct sessions_data	 sd;
-	struct windows_data	 wd;
-	struct pollfd	 	 pfd;
-	struct hdr		 hdr;
-
-	/* Send query data. */
-	if (*name == '\0') {
-		hdr.type = MSG_SESSIONS;
-		hdr.size = sizeof sd;
-		buffer_write(server_out, &hdr, sizeof hdr);
-		buffer_write(server_out, &sd, hdr.size);
-	} else {
-		hdr.type = MSG_WINDOWS;
-		hdr.size = sizeof wd;
-		buffer_write(server_out, &hdr, sizeof hdr);
-		strlcpy(wd.name, name, sizeof wd.name);
-		buffer_write(server_out, &wd, hdr.size);
-	}
-
-	/* Main loop. */
-	for (;;) {
-		/* Set up pollfd. */
-		pfd.fd = server_fd;
-		pfd.events = POLLIN;
-		if (BUFFER_USED(server_out) > 0)
-			pfd.events |= POLLOUT;
-
-		/* Do the poll. */
-		if (poll(&pfd, 1, INFTIM) == -1) {
-			if (errno == EAGAIN || errno == EINTR)
-				continue;
-			err(1, "poll");
-		}
-
-		/* Read/write from sockets. */
-		if (buffer_poll(&pfd, server_in, server_out) != 0)
-			errx(1, "lost server"); 
-
-		/* Process data. */
-		process_list(name);
-	}
-}
-
-void
-process_list(const char *name)
-{
-	struct sessions_data	 sd;
-	struct sessions_entry	 se;
-	struct windows_data	 wd;
-	struct windows_entry	 we;
-	struct hdr		 hdr;
-	char		        *tim;
-	
-	for (;;) {
-		if (BUFFER_USED(server_in) < sizeof hdr)
-			break;
-		memcpy(&hdr, BUFFER_OUT(server_in), sizeof hdr);
-		if (BUFFER_USED(server_in) < (sizeof hdr) + hdr.size)
-			break;
-		buffer_remove(server_in, sizeof hdr);
-		
-		switch (hdr.type) {
-		case MSG_SESSIONS:
-			if (hdr.size < sizeof sd)
-				errx(1, "bad MSG_SESSIONS size");
-			buffer_read(server_in, &sd, sizeof sd);
-			hdr.size -= sizeof sd; 
-			if (sd.sessions == 0 && hdr.size == 0)
-				exit(0);
-			if (hdr.size < sd.sessions * sizeof se)
-				errx(1, "bad MSG_SESSIONS size");
-			while (sd.sessions-- > 0) {
-				buffer_read(server_in, &se, sizeof se);
-				tim = ctime(&se.tim);
-				*strchr(tim, '\n') = '\0';
-				printf("%s: %u windows (created %s)\n",
-				    se.name, se.windows, tim);
-			}
-			exit(0);
-		case MSG_WINDOWS:
-			if (hdr.size < sizeof wd)
-				errx(1, "bad MSG_WINDOWS size");
-			buffer_read(server_in, &wd, sizeof wd);
-			hdr.size -= sizeof wd; 
-			if (wd.windows == 0 && hdr.size == 0)
-				errx(1, "session not found: %s", name);
-			if (hdr.size < wd.windows * sizeof we)
-				errx(1, "bad MSG_WINDOWS size");
-			while (wd.windows-- > 0) {
-				buffer_read(server_in, &we, sizeof we);
-				if (*we.title != '\0') {
-					printf("%u: %s \"%s\" (%s)\n",
-					    we.idx, we.name, we.title, we.tty); 
-				} else {
-					printf("%u: %s (%s)\n",
-					    we.idx, we.name, we.tty);
-				}
-			}
-			exit(0);
-		default:
-			fatalx("unexpected message");
-		}
-	}
+	exit(usage(NULL));
 }
diff --git a/tmux.h b/tmux.h
index cb10a3cf..c35a951f 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1,4 +1,4 @@
-/* $Id: tmux.h,v 1.11 2007-09-26 10:35:24 nicm Exp $ */
+/* $Id: tmux.h,v 1.12 2007-09-26 13:43:15 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -257,18 +257,11 @@ struct buffer {
 #define CODE_KKEYPADON 25
 #define CODE_TITLE 26
 
-/* Command-line commands. */
-enum op {
-	OP_LIST = 0,
-	OP_NEW,
-	OP_ATTACH
-};
-
 /* Message codes. */
 enum hdrtype {
 	MSG_NEW = 0,
 	MSG_ATTACH,
-	MSG_READY,
+	MSG_ERROR,
 	MSG_CREATE,
 	MSG_EXIT,
 	MSG_SIZE,
@@ -423,17 +416,50 @@ struct client {
 };
 ARRAY_DECL(clients, struct client *);
 
+/* Client context. */
+struct client_ctx {
+	int		 srv_fd;
+	struct buffer	*srv_in;
+	struct buffer	*srv_out;
+
+	int		 loc_fd;
+	struct buffer	*loc_in;
+	struct buffer	*loc_out;
+
+	struct winsize	 ws;
+};
+
 /* tmux.c */
-volatile sig_atomic_t sigterm;
-extern int   debug_level;
-extern char  socket_path[MAXPATHLEN];
+extern volatile sig_atomic_t sigwinch;
+extern volatile sig_atomic_t sigterm;
+extern int debug_level;
+int	 	 usage(const char *);
+void		 logfile(const char *);
+void		 siginit(void);
+void		 sigreset(void);
+
+/* op.c */
+int	 op_new(char *, int, char **);
+int	 op_attach(char *, int, char **);
+
+/* client.c */
+int	 client_init(char *, struct client_ctx *, int);
+int	 client_main(struct client_ctx *);
+void	 client_write_server(struct client_ctx *, enum hdrtype, void *, size_t);
+
+/* client-msg.c */
+int	 client_msg_dispatch(struct client_ctx *, const char **);
+
+/* command.c */
+extern int client_cmd_prefix;
+int	 client_cmd_dispatch(int, struct client_ctx *, const char **);
 
 /* server.c */
 extern struct clients clients;
-int	 server_start(void);
+int	 server_start(char *);
 
 /* server-msg.c */
-void	 server_msg_dispatch(struct client *);
+int	 server_msg_dispatch(struct client *);
 
 /* server-fn.c */
 void	 write_message(struct client *, const char *, ...);
@@ -468,10 +494,6 @@ void	 local_done(void);
 int	 local_key(size_t *);
 void	 local_output(struct buffer *, size_t);
 
-/* command.c */
-extern int cmd_prefix;
-int	 cmd_execute(int, struct buffer *);
-
 /* window.c */
 extern struct windows windows;
 struct window	*window_create(const char *, u_int, u_int);