From ddc4512d2e0eda6c705e002cb5dbf80719d709e1 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Sun, 16 Oct 2016 17:55:14 +0000
Subject: [PATCH] Rewrite command queue handling. Each client still has a
 command queue, but there is also now a global command queue. Instead of
 command queues being dispatched on demand from wherever the command happens
 to be added, they are now all dispatched from the top level server loop.
 Command queues may now also include callbacks as well as commands, and items
 may be inserted after the current command as well as at the end.

This all makes command queues significantly more predictable and easier
to use, and avoids the complex multiple nested command queues used by
source-file, if-shell and friends.

A mass rename of struct cmdq to a better name (cmdq_item probably) is
coming.
---
 cfg.c                |  70 +++----
 cmd-attach-session.c |   2 +-
 cmd-command-prompt.c |  30 ++-
 cmd-confirm-before.c |  27 ++-
 cmd-copy-mode.c      |   4 +-
 cmd-display-panes.c  |  48 +++--
 cmd-find.c           |   2 +-
 cmd-if-shell.c       | 110 +++++-----
 cmd-load-buffer.c    |  45 ++--
 cmd-new-session.c    |   6 +-
 cmd-new-window.c     |   4 +-
 cmd-queue.c          | 489 ++++++++++++++++++++++++++-----------------
 cmd-resize-pane.c    |   4 +-
 cmd-run-shell.c      |  28 ++-
 cmd-send-keys.c      |   2 +-
 cmd-source-file.c    |  54 ++---
 cmd-split-window.c   |   4 +-
 cmd-wait-for.c       |  69 +++---
 cmd.c                |  51 ++---
 control.c            |  29 ++-
 format.c             |   9 +-
 hooks.c              |  68 ++----
 key-bindings.c       |  22 +-
 notify.c             |  12 +-
 server-client.c      |  44 ++--
 server.c             |  10 +-
 tmux.1               |   4 +-
 tmux.h               | 268 ++++++++++++------------
 window-choose.c      |   4 +-
 29 files changed, 817 insertions(+), 702 deletions(-)

diff --git a/cfg.c b/cfg.c
index 801056db..62cca179 100644
--- a/cfg.c
+++ b/cfg.c
@@ -29,14 +29,25 @@
 #include "tmux.h"
 
 char			 *cfg_file;
-static struct cmd_q	 *cfg_cmd_q;
 int			  cfg_finished;
-int			  cfg_references;
 static char		**cfg_causes;
 static u_int		  cfg_ncauses;
 struct client		 *cfg_client;
 
-static void	  cfg_default_done(struct cmd_q *);
+static enum cmd_retval
+cfg_done(__unused struct cmd_q *cmdq, __unused void *data)
+{
+	if (cfg_finished)
+		return (CMD_RETURN_NORMAL);
+	cfg_finished = 1;
+
+	if (!RB_EMPTY(&sessions))
+		cfg_show_causes(RB_MIN(sessions, &sessions));
+
+	if (cfg_client != NULL)
+		server_client_unref(cfg_client);
+	return (CMD_RETURN_NORMAL);
+}
 
 void
 set_cfg_file(const char *path)
@@ -51,30 +62,24 @@ start_cfg(void)
 	const char	*home;
 	int		 quiet = 0;
 
-	cfg_cmd_q = cmdq_new(NULL);
-	cfg_cmd_q->emptyfn = cfg_default_done;
-
-	cfg_finished = 0;
-	cfg_references = 1;
-
 	cfg_client = TAILQ_FIRST(&clients);
 	if (cfg_client != NULL)
 		cfg_client->references++;
 
-	load_cfg(TMUX_CONF, cfg_cmd_q, 1);
+	load_cfg(TMUX_CONF, cfg_client, NULL, 1);
 
 	if (cfg_file == NULL && (home = find_home()) != NULL) {
 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
 		quiet = 1;
 	}
 	if (cfg_file != NULL)
-		load_cfg(cfg_file, cfg_cmd_q, quiet);
+		load_cfg(cfg_file, cfg_client, NULL, quiet);
 
-	cmdq_continue(cfg_cmd_q);
+	cmdq_append(cfg_client, cmdq_get_callback(cfg_done, NULL));
 }
 
 int
-load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
+load_cfg(const char *path, struct client *c, struct cmd_q *cmdq, int quiet)
 {
 	FILE		*f;
 	char		 delim[3] = { '\\', '\\', '\0' };
@@ -82,6 +87,7 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 	size_t		 line = 0;
 	char		*buf, *cause1, *p;
 	struct cmd_list	*cmdlist;
+	struct cmd_q	*new_cmdq;
 
 	log_debug("loading %s", path);
 	if ((f = fopen(path, "rb")) == NULL) {
@@ -117,8 +123,13 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 
 		if (cmdlist == NULL)
 			continue;
-		cmdq_append(cmdq, cmdlist, NULL);
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		if (cmdq != NULL)
+			cmdq_insert_after(cmdq, new_cmdq);
+		else
+			cmdq_append(c, new_cmdq);
 		cmd_list_free(cmdlist);
+
 		found++;
 	}
 	fclose(f);
@@ -126,37 +137,6 @@ load_cfg(const char *path, struct cmd_q *cmdq, int quiet)
 	return (found);
 }
 
-static void
-cfg_default_done(__unused struct cmd_q *cmdq)
-{
-	log_debug("%s: %u references%s", __func__, cfg_references,
-	    cfg_finished ? " (finished)" : "");
-
-	if (cfg_finished || --cfg_references != 0)
-		return;
-	cfg_finished = 1;
-
-	if (!RB_EMPTY(&sessions))
-		cfg_show_causes(RB_MIN(sessions, &sessions));
-
-	cmdq_free(cfg_cmd_q);
-	cfg_cmd_q = NULL;
-
-	if (cfg_client != NULL) {
-		/*
-		 * The client command queue starts with client_exit set to 1 so
-		 * only continue if not empty (that is, we have been delayed
-		 * during configuration parsing for long enough that the
-		 * MSG_COMMAND has arrived), else the client will exit before
-		 * the MSG_COMMAND which might tell it not to.
-		 */
-		if (!TAILQ_EMPTY(&cfg_client->cmdq->queue))
-			cmdq_continue(cfg_client->cmdq);
-		server_client_unref(cfg_client);
-		cfg_client = NULL;
-	}
-}
-
 void
 cfg_add_cause(const char *fmt, ...)
 {
diff --git a/cmd-attach-session.c b/cmd-attach-session.c
index daab428f..f0e860f9 100644
--- a/cmd-attach-session.c
+++ b/cmd-attach-session.c
@@ -145,7 +145,7 @@ cmd_attach_session(struct cmd_q *cmdq, int dflag, int rflag, const char *cflag,
 		if (~c->flags & CLIENT_CONTROL)
 			proc_send(c->peer, MSG_READY, -1, NULL, 0);
 		hooks_run(c->session->hooks, c, NULL, "client-attached");
-		cmdq->client_exit = 0;
+		c->flags |= CLIENT_ATTACHED;
 	}
 	recalculate_sizes();
 	alerts_check_session(s);
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 12ecb493..5e21b2bc 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -121,12 +121,24 @@ cmd_command_prompt_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_command_prompt_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static int
 cmd_command_prompt_callback(void *data, const char *s)
 {
 	struct cmd_command_prompt_cdata	*cdata = data;
 	struct client			*c = cdata->c;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	char				*cause, *new_template, *prompt, *ptr;
 	char				*input = NULL;
 
@@ -153,17 +165,19 @@ cmd_command_prompt_callback(void *data, const char *s)
 
 	if (cmd_string_parse(new_template, &cmdlist, NULL, 0, &cause) != 0) {
 		if (cause != NULL) {
-			*cause = toupper((u_char) *cause);
-			status_message_set(c, "%s", cause);
-			free(cause);
-		}
-		return (0);
+			new_cmdq = cmdq_get_callback(cmd_command_prompt_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq_run(c->cmdq, cmdlist, NULL);
-	cmd_list_free(cmdlist);
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
 
-	if (c->prompt_callbackfn != (void *) &cmd_command_prompt_callback)
+	if (c->prompt_callbackfn != (void *)&cmd_command_prompt_callback)
 		return (1);
 	return (0);
 }
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
index 4241aefb..2dd52c81 100644
--- a/cmd-confirm-before.c
+++ b/cmd-confirm-before.c
@@ -83,12 +83,24 @@ cmd_confirm_before_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_confirm_before_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static int
 cmd_confirm_before_callback(void *data, const char *s)
 {
 	struct cmd_confirm_before_data	*cdata = data;
 	struct client			*c = cdata->client;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	char				*cause;
 
 	if (c->flags & CLIENT_DEAD)
@@ -101,14 +113,17 @@ cmd_confirm_before_callback(void *data, const char *s)
 
 	if (cmd_string_parse(cdata->cmd, &cmdlist, NULL, 0, &cause) != 0) {
 		if (cause != NULL) {
-			cmdq_error(c->cmdq, "%s", cause);
-			free(cause);
-		}
-		return (0);
+			new_cmdq = cmdq_get_callback(cmd_confirm_before_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq_run(c->cmdq, cmdlist, NULL);
-	cmd_list_free(cmdlist);
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
 
 	return (0);
 }
diff --git a/cmd-copy-mode.c b/cmd-copy-mode.c
index 4591a37d..dc880d56 100644
--- a/cmd-copy-mode.c
+++ b/cmd-copy-mode.c
@@ -61,7 +61,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct window_pane	*wp = cmdq->state.tflag.wp;
 
 	if (args_has(args, 'M')) {
-		if ((wp = cmd_mouse_pane(&cmdq->item->mouse, &s, NULL)) == NULL)
+		if ((wp = cmd_mouse_pane(&cmdq->mouse, &s, NULL)) == NULL)
 			return (CMD_RETURN_NORMAL);
 		if (c == NULL || c->session != s)
 			return (CMD_RETURN_NORMAL);
@@ -80,7 +80,7 @@ cmd_copy_mode_exec(struct cmd *self, struct cmd_q *cmdq)
 	if (args_has(args, 'M')) {
 		if (wp->mode != NULL && wp->mode != &window_copy_mode)
 			return (CMD_RETURN_NORMAL);
-		window_copy_start_drag(c, &cmdq->item->mouse);
+		window_copy_start_drag(c, &cmdq->mouse);
 	}
 	if (wp->mode == &window_copy_mode && args_has(self->args, 'u'))
 		window_copy_pageup(wp, 0);
diff --git a/cmd-display-panes.c b/cmd-display-panes.c
index 2edb2eb3..471bec02 100644
--- a/cmd-display-panes.c
+++ b/cmd-display-panes.c
@@ -65,32 +65,48 @@ cmd_display_panes_exec(struct cmd *self, struct cmd_q *cmdq)
 	return (CMD_RETURN_NORMAL);
 }
 
+static enum cmd_retval
+cmd_display_panes_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static void
 cmd_display_panes_callback(struct client *c, struct window_pane *wp)
 {
 	struct cmd_list	*cmdlist;
+	struct cmd_q	*new_cmdq;
 	char		*template, *cmd, *expanded, *cause;
 
 	template = c->identify_callback_data;
-	if (wp != NULL) {
-		xasprintf(&expanded, "%%%u", wp->id);
-		cmd = cmd_template_replace(template, expanded, 1);
+	if (wp == NULL)
+		goto out;
+	xasprintf(&expanded, "%%%u", wp->id);
+	cmd = cmd_template_replace(template, expanded, 1);
 
-		if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) {
-			if (cause != NULL) {
-				*cause = toupper((u_char) *cause);
-				status_message_set(c, "%s", cause);
-				free(cause);
-			}
-		} else {
-			cmdq_run(c->cmdq, cmdlist, NULL);
-			cmd_list_free(cmdlist);
-		}
-
-		free(cmd);
-		free(expanded);
+	if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) {
+		if (cause != NULL) {
+			new_cmdq = cmdq_get_callback(cmd_display_panes_error,
+			    cause);
+		} else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+		cmd_list_free(cmdlist);
 	}
 
+	if (new_cmdq != NULL)
+		cmdq_append(c, new_cmdq);
+
+	free(cmd);
+	free(expanded);
+
+out:
 	free(c->identify_callback_data);
 	c->identify_callback_data = NULL;
 	c->identify_callback = NULL;
diff --git a/cmd-find.c b/cmd-find.c
index 21691f67..f9cbe442 100644
--- a/cmd-find.c
+++ b/cmd-find.c
@@ -1006,7 +1006,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmd_find_state *current,
 
 	/* Mouse target is a plain = or {mouse}. */
 	if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) {
-		m = &cmdq->item->mouse;
+		m = &cmdq->mouse;
 		switch (type) {
 		case CMD_FIND_PANE:
 			fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl);
diff --git a/cmd-if-shell.c b/cmd-if-shell.c
index 2a8cbff2..7192b204 100644
--- a/cmd-if-shell.c
+++ b/cmd-if-shell.c
@@ -31,9 +31,9 @@
 
 static enum cmd_retval	 cmd_if_shell_exec(struct cmd *, struct cmd_q *);
 
-static void	cmd_if_shell_callback(struct job *);
-static void	cmd_if_shell_done(struct cmd_q *);
-static void	cmd_if_shell_free(void *);
+static enum cmd_retval	 cmd_if_shell_error(struct cmd_q *, void *);
+static void		 cmd_if_shell_callback(struct job *);
+static void		 cmd_if_shell_free(void *);
 
 const struct cmd_entry cmd_if_shell_entry = {
 	.name = "if-shell",
@@ -56,11 +56,9 @@ struct cmd_if_shell_data {
 	char			*cmd_if;
 	char			*cmd_else;
 
+	struct client		*client;
 	struct cmd_q		*cmdq;
 	struct mouse_event	 mouse;
-
-	int			 bflag;
-	int			 references;
 };
 
 static enum cmd_retval
@@ -70,6 +68,7 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct cmd_if_shell_data	*cdata;
 	char				*shellcmd, *cmd, *cause;
 	struct cmd_list			*cmdlist;
+	struct cmd_q			*new_cmdq;
 	struct session			*s = cmdq->state.tflag.s;
 	struct winlink			*wl = cmdq->state.tflag.wl;
 	struct window_pane		*wp = cmdq->state.tflag.wp;
@@ -104,7 +103,8 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 			}
 			return (CMD_RETURN_ERROR);
 		}
-		cmdq_run(cmdq, cmdlist, &cmdq->item->mouse);
+		new_cmdq = cmdq_get_command(cmdlist, NULL, &cmdq->mouse, 0);
+		cmdq_insert_after(cmdq, new_cmdq);
 		cmd_list_free(cmdlist);
 		return (CMD_RETURN_NORMAL);
 	}
@@ -121,92 +121,80 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 	else
 		cdata->cmd_else = NULL;
 
-	cdata->bflag = args_has(args, 'b');
+	cdata->client = cmdq->client;
+	cdata->client->references++;
 
-	cdata->cmdq = cmdq;
-	memcpy(&cdata->mouse, &cmdq->item->mouse, sizeof cdata->mouse);
-	cmdq->references++;
+	if (!args_has(args, 'b'))
+		cdata->cmdq = cmdq;
+	else
+		cdata->cmdq = NULL;
+	memcpy(&cdata->mouse, &cmdq->mouse, sizeof cdata->mouse);
 
-	cdata->references = 1;
 	job_run(shellcmd, s, cwd, cmd_if_shell_callback, cmd_if_shell_free,
 	    cdata);
 	free(shellcmd);
 
-	if (cdata->bflag)
+	if (args_has(args, 'b'))
 		return (CMD_RETURN_NORMAL);
 	return (CMD_RETURN_WAIT);
 }
 
+static enum cmd_retval
+cmd_if_shell_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 static void
 cmd_if_shell_callback(struct job *job)
 {
 	struct cmd_if_shell_data	*cdata = job->data;
-	struct cmd_q			*cmdq = cdata->cmdq, *cmdq1;
+	struct client			*c = cdata->client;
 	struct cmd_list			*cmdlist;
-	char				*cause, *cmd;
-
-	if (cmdq->flags & CMD_Q_DEAD)
-		return;
+	struct cmd_q			*new_cmdq;
+	char				*cause, *cmd, *file = cdata->file;
+	u_int				 line = cdata->line;
 
 	if (!WIFEXITED(job->status) || WEXITSTATUS(job->status) != 0)
 		cmd = cdata->cmd_else;
 	else
 		cmd = cdata->cmd_if;
 	if (cmd == NULL)
-		return;
+		goto out;
 
-	if (cmd_string_parse(cmd, &cmdlist, cdata->file, cdata->line,
-	    &cause) != 0) {
-		if (cause != NULL) {
-			cmdq_error(cmdq, "%s", cause);
-			free(cause);
-		}
-		return;
+	if (cmd_string_parse(cmd, &cmdlist, file, line, &cause) != 0) {
+		if (cause != NULL)
+			new_cmdq = cmdq_get_callback(cmd_if_shell_error, cause);
+		else
+			new_cmdq = NULL;
+	} else {
+		new_cmdq = cmdq_get_command(cmdlist, NULL, &cdata->mouse, 0);
+		cmd_list_free(cmdlist);
 	}
 
-	cmdq1 = cmdq_new(cmdq->client);
-	cmdq1->emptyfn = cmd_if_shell_done;
-	cmdq1->data = cdata;
+	if (new_cmdq != NULL) {
+		if (cdata->cmdq == NULL)
+			cmdq_append(c, new_cmdq);
+		else
+			cmdq_insert_after(cdata->cmdq, new_cmdq);
+	}
 
-	cdata->references++;
-	cmdq_run(cmdq1, cmdlist, &cdata->mouse);
-	cmd_list_free(cmdlist);
-}
-
-static void
-cmd_if_shell_done(struct cmd_q *cmdq1)
-{
-	struct cmd_if_shell_data	*cdata = cmdq1->data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-
-	if (cmdq1->client_exit >= 0)
-		cmdq->client_exit = cmdq1->client_exit;
-	cmdq_free(cmdq1);
-
-	if (--cdata->references != 0)
-		return;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
-
-	free(cdata->cmd_else);
-	free(cdata->cmd_if);
-
-	free(cdata->file);
-	free(cdata);
+out:
+	if (cdata->cmdq != NULL)
+		cdata->cmdq->flags &= ~CMD_Q_WAITING;
 }
 
 static void
 cmd_if_shell_free(void *data)
 {
 	struct cmd_if_shell_data	*cdata = data;
-	struct cmd_q			*cmdq = cdata->cmdq;
 
-	if (--cdata->references != 0)
-		return;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
+	server_client_unref(cdata->client);
 
 	free(cdata->cmd_else);
 	free(cdata->cmd_if);
diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c
index ca886d69..ae071968 100644
--- a/cmd-load-buffer.c
+++ b/cmd-load-buffer.c
@@ -46,17 +46,24 @@ const struct cmd_entry cmd_load_buffer_entry = {
 	.exec = cmd_load_buffer_exec
 };
 
+struct cmd_load_buffer_data {
+	struct cmd_q	*cmdq;
+	char		*bufname;
+};
+
 static enum cmd_retval
 cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
 {
-	struct args	*args = self->args;
-	struct client	*c = cmdq->client;
-	struct session  *s;
-	FILE		*f;
-	const char	*path, *bufname, *cwd;
-	char		*pdata, *new_pdata, *cause, *file, resolved[PATH_MAX];
-	size_t		 psize;
-	int		 ch, error;
+	struct args			*args = self->args;
+	struct cmd_load_buffer_data	*cdata;
+	struct client			*c = cmdq->client;
+	struct session  		*s;
+	FILE				*f;
+	const char			*path, *bufname, *cwd;
+	char				*pdata, *new_pdata, *cause, *file;
+	char				 resolved[PATH_MAX];
+	size_t				 psize;
+	int				 ch, error;
 
 	bufname = NULL;
 	if (args_has(args, 'b'))
@@ -64,8 +71,12 @@ cmd_load_buffer_exec(struct cmd *self, struct cmd_q *cmdq)
 
 	path = args->argv[0];
 	if (strcmp(path, "-") == 0) {
+		cdata = xcalloc(1, sizeof *cdata);
+		cdata->cmdq = cmdq;
+		cdata->bufname = xstrdup(bufname);
+
 		error = server_set_stdin_callback(c, cmd_load_buffer_callback,
-		    (void *)bufname, &cause);
+		    cdata, &cause);
 		if (error != 0) {
 			cmdq_error(cmdq, "%s: %s", path, cause);
 			free(cause);
@@ -136,9 +147,9 @@ error:
 static void
 cmd_load_buffer_callback(struct client *c, int closed, void *data)
 {
-	const char	*bufname = data;
-	char		*pdata, *cause, *saved;
-	size_t		 psize;
+	struct cmd_load_buffer_data	*cdata = data;
+	char				*pdata, *cause, *saved;
+	size_t				 psize;
 
 	if (!closed)
 		return;
@@ -146,7 +157,7 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 
 	server_client_unref(c);
 	if (c->flags & CLIENT_DEAD)
-		return;
+		goto out;
 
 	psize = EVBUFFER_LENGTH(c->stdin_data);
 	if (psize == 0 || (pdata = malloc(psize + 1)) == NULL)
@@ -156,7 +167,7 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 	pdata[psize] = '\0';
 	evbuffer_drain(c->stdin_data, psize);
 
-	if (paste_set(pdata, psize, bufname, &cause) != 0) {
+	if (paste_set(pdata, psize, cdata->bufname, &cause) != 0) {
 		/* No context so can't use server_client_msg_error. */
 		if (~c->flags & CLIENT_UTF8) {
 			saved = cause;
@@ -168,7 +179,9 @@ cmd_load_buffer_callback(struct client *c, int closed, void *data)
 		free(pdata);
 		free(cause);
 	}
-
 out:
-	cmdq_continue(c->cmdq);
+	cdata->cmdq->flags &= ~CMD_Q_WAITING;
+
+	free(cdata->bufname);
+	free(cdata);
 }
diff --git a/cmd-new-session.c b/cmd-new-session.c
index 5924b793..e587e5ee 100644
--- a/cmd-new-session.c
+++ b/cmd-new-session.c
@@ -312,14 +312,14 @@ cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq)
 	}
 
 	if (!detached)
-		cmdq->client_exit = 0;
+		c->flags |= CLIENT_ATTACHED;
 
 	if (to_free != NULL)
 		free((void *)to_free);
 
 	cmd_find_from_session(&fs, s);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-new-session") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-new-session");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-new-window.c b/cmd-new-window.c
index 4d0e0057..af476913 100644
--- a/cmd-new-window.c
+++ b/cmd-new-window.c
@@ -157,8 +157,8 @@ cmd_new_window_exec(struct cmd *self, struct cmd_q *cmdq)
 		free((void *)to_free);
 
 	cmd_find_from_winlink(&fs, s, wl);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-new-window") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-new-window");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-queue.c b/cmd-queue.c
index 2012e871..fca3bf04 100644
--- a/cmd-queue.c
+++ b/cmd-queue.c
@@ -25,47 +25,316 @@
 
 #include "tmux.h"
 
-static enum cmd_retval	cmdq_continue_one(struct cmd_q *);
-static void		cmdq_flush(struct cmd_q *);
+/* Global command queue. */
+static struct cmd_q_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue);
 
-/* Create new command queue. */
+/* Get command queue name. */
+static const char *
+cmdq_name(struct client *c)
+{
+	static char	s[32];
+
+	if (c == NULL)
+		return ("<global>");
+	xsnprintf(s, sizeof s, "<%p>", c);
+	return (s);
+}
+
+/* Get command queue from client. */
+static struct cmd_q_list *
+cmdq_get(struct client *c)
+{
+	if (c == NULL)
+		return (&global_queue);
+	return (&c->queue);
+}
+
+/* Append an item. */
+void
+cmdq_append(struct client *c, struct cmd_q *cmdq)
+{
+	struct cmd_q_list	*queue = cmdq_get(c);
+	struct cmd_q		*next;
+
+	do {
+		next = cmdq->next;
+		cmdq->next = NULL;
+
+		if (c != NULL)
+			c->references++;
+		cmdq->client = c;
+
+		cmdq->queue = queue;
+		TAILQ_INSERT_TAIL(queue, cmdq, entry);
+
+		cmdq = next;
+	} while (cmdq != NULL);
+}
+
+/* Insert an item. */
+void
+cmdq_insert_after(struct cmd_q *after, struct cmd_q *cmdq)
+{
+	struct client		*c = after->client;
+	struct cmd_q_list	*queue = after->queue;
+	struct cmd_q		*next;
+
+	do {
+		next = cmdq->next;
+		cmdq->next = NULL;
+
+		if (c != NULL)
+			c->references++;
+		cmdq->client = c;
+
+		cmdq->queue = queue;
+		if (after->next != NULL)
+			TAILQ_INSERT_AFTER(queue, after->next, cmdq, entry);
+		else
+			TAILQ_INSERT_AFTER(queue, after, cmdq, entry);
+		after->next = cmdq;
+
+		cmdq = next;
+	} while (cmdq != NULL);
+}
+
+/* Remove an item. */
+static void
+cmdq_remove(struct cmd_q *cmdq)
+{
+	free((void *)cmdq->hook);
+
+	if (cmdq->client != NULL)
+		server_client_unref(cmdq->client);
+
+	if (cmdq->type == CMD_Q_COMMAND)
+		cmd_list_free(cmdq->cmdlist);
+
+	TAILQ_REMOVE(cmdq->queue, cmdq, entry);
+	free(cmdq);
+}
+
+/* Set command group. */
+static u_int
+cmdq_next_group(void)
+{
+	static u_int	group;
+
+	return (++group);
+}
+
+/* Remove all subsequent items that match this item's group. */
+static void
+cmdq_remove_group(struct cmd_q *cmdq)
+{
+	struct cmd_q	*this, *next;
+
+	this = TAILQ_NEXT(cmdq, entry);
+	while (this != NULL) {
+		next = TAILQ_NEXT(this, entry);
+		if (this->group == cmdq->group)
+			cmdq_remove(this);
+		this = next;
+	}
+}
+
+/* Get a command for the command queue. */
 struct cmd_q *
-cmdq_new(struct client *c)
+cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
+    struct mouse_event *m, int flags)
+{
+	struct cmd_q	*cmdq, *first = NULL, *last = NULL;
+	struct cmd	*cmd;
+	u_int		 group = cmdq_next_group();
+
+	TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
+		cmdq = xcalloc(1, sizeof *cmdq);
+		cmdq->type = CMD_Q_COMMAND;
+		cmdq->group = group;
+		cmdq->flags = flags;
+
+		cmdq->cmdlist = cmdlist;
+		cmdq->cmd = cmd;
+
+		if (current != NULL)
+			cmd_find_copy_state(&cmdq->current, current);
+		if (m != NULL)
+			memcpy(&cmdq->mouse, m, sizeof cmdq->mouse);
+		cmdlist->references++;
+
+		if (first == NULL)
+			first = cmdq;
+		if (last != NULL)
+			last->next = cmdq;
+		last = cmdq;
+	}
+	return (first);
+}
+
+/* Fire command on command queue. */
+static enum cmd_retval
+cmdq_fire_command(struct cmd_q *cmdq)
+{
+	struct client		*c = cmdq->client;
+	struct cmd		*cmd = cmdq->cmd;
+	enum cmd_retval		 retval;
+	const char		*name;
+	struct cmd_find_state	*fsp, fs;
+	int			 flags;
+
+	flags = !!(cmd->flags & CMD_CONTROL);
+	cmdq_guard(cmdq, "begin", flags);
+
+	if (cmd_prepare_state(cmd, cmdq) != 0) {
+		retval = CMD_RETURN_ERROR;
+		goto out;
+	}
+	if (cmdq->client == NULL)
+		cmdq->client = cmd_find_client(cmdq, NULL, CMD_FIND_QUIET);
+
+	retval = cmd->entry->exec(cmd, cmdq);
+	if (retval == CMD_RETURN_ERROR)
+		goto out;
+
+	if (cmd->entry->flags & CMD_AFTERHOOK) {
+		name = cmd->entry->name;
+		if (cmd_find_valid_state(&cmdq->state.tflag))
+			fsp = &cmdq->state.tflag;
+		else {
+			if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
+				goto out;
+			fsp = &fs;
+		}
+		hooks_insert(fsp->s->hooks, cmdq, fsp, "after-%s", name);
+	}
+
+out:
+	cmdq->client = c;
+	if (retval == CMD_RETURN_ERROR)
+		cmdq_guard(cmdq, "error", flags);
+	else
+		cmdq_guard(cmdq, "end", flags);
+	return (retval);
+}
+
+/* Get a callback for the command queue. */
+struct cmd_q *
+cmdq_get_callback(cmd_q_cb cb, void *data)
 {
 	struct cmd_q	*cmdq;
 
 	cmdq = xcalloc(1, sizeof *cmdq);
-	cmdq->references = 1;
+	cmdq->type = CMD_Q_CALLBACK;
+	cmdq->group = 0;
 	cmdq->flags = 0;
 
-	cmdq->client = c;
-	cmdq->client_exit = -1;
-
-	TAILQ_INIT(&cmdq->queue);
-	cmdq->item = NULL;
-	cmdq->cmd = NULL;
-
-	cmd_find_clear_state(&cmdq->current, NULL, 0);
-	cmdq->parent = NULL;
+	cmdq->cb = cb;
+	cmdq->data = data;
 
 	return (cmdq);
 }
 
-/* Free command queue */
-int
-cmdq_free(struct cmd_q *cmdq)
+/* Fire callback on callback queue. */
+static enum cmd_retval
+cmdq_fire_callback(struct cmd_q *cmdq)
 {
-	log_debug("cmdq %p free: %u references", cmdq, cmdq->references);
+	return (cmdq->cb(cmdq, cmdq->data));
+}
 
-	if (--cmdq->references != 0) {
-		if (cmdq->flags & CMD_Q_DEAD)
-			return (1);
+/* Process next item on command queue. */
+u_int
+cmdq_next(struct client *c)
+{
+	struct cmd_q_list	*queue = cmdq_get(c);
+	const char		*name = cmdq_name(c);
+	struct cmd_q		*cmdq;
+	enum cmd_retval		 retval;
+	u_int			 items = 0;
+	static u_int		 number;
+
+	if (TAILQ_EMPTY(queue)) {
+		log_debug("%s %s: empty", __func__, name);
+		return (0);
+	}
+	if (TAILQ_FIRST(queue)->flags & CMD_Q_WAITING) {
+		log_debug("%s %s: waiting", __func__, name);
 		return (0);
 	}
 
-	cmdq_flush(cmdq);
-	free(cmdq);
-	return (1);
+	log_debug("%s %s: enter", __func__, name);
+	for (;;) {
+		cmdq = TAILQ_FIRST(queue);
+		if (cmdq == NULL)
+			break;
+		log_debug("%s %s: type %d, flags %x", __func__, name,
+		    cmdq->type, cmdq->flags);
+
+		/*
+		 * Any item with the waiting flag set waits until an external
+		 * event clears the flag (for example, a job - look at
+		 * run-shell).
+		 */
+		if (cmdq->flags & CMD_Q_WAITING)
+			goto waiting;
+
+		/*
+		 * Items are only fired once, once the fired flag is set, a
+		 * waiting flag can only be cleared by an external event.
+		 */
+		if (~cmdq->flags & CMD_Q_FIRED) {
+			cmdq->time = time(NULL);
+			cmdq->number = ++number;
+
+			switch (cmdq->type)
+			{
+			case CMD_Q_COMMAND:
+				retval = cmdq_fire_command(cmdq);
+
+				/*
+				 * If a command returns an error, remove any
+				 * subsequent commands in the same group.
+				 */
+				if (retval == CMD_RETURN_ERROR)
+					cmdq_remove_group(cmdq);
+				break;
+			case CMD_Q_CALLBACK:
+				retval = cmdq_fire_callback(cmdq);
+				break;
+			default:
+				retval = CMD_RETURN_ERROR;
+				break;
+			}
+			cmdq->flags |= CMD_Q_FIRED;
+
+			if (retval == CMD_RETURN_WAIT) {
+				cmdq->flags |= CMD_Q_WAITING;
+				goto waiting;
+			}
+			items++;
+		}
+		cmdq_remove(cmdq);
+	}
+
+	log_debug("%s %s: exit (empty)", __func__, name);
+	return (items);
+
+waiting:
+	log_debug("%s %s: exit (wait)", __func__, name);
+	return (items);
+}
+
+/* Print a guard line. */
+void
+cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
+{
+	struct client	*c = cmdq->client;
+
+	if (c == NULL || !(c->flags & CLIENT_CONTROL))
+		return;
+
+	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
+	    (long)cmdq->time, cmdq->number, flags);
+	server_client_push_stdout(c);
 }
 
 /* Show message from command. */
@@ -140,175 +409,3 @@ cmdq_error(struct cmd_q *cmdq, const char *fmt, ...)
 
 	free(msg);
 }
-
-/* Print a guard line. */
-void
-cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
-{
-	struct client	*c = cmdq->client;
-
-	if (c == NULL || !(c->flags & CLIENT_CONTROL))
-		return;
-
-	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
-	    (long) cmdq->time, cmdq->number, flags);
-	server_client_push_stdout(c);
-}
-
-/* Add command list to queue and begin processing if needed. */
-void
-cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
-{
-	cmdq_append(cmdq, cmdlist, m);
-
-	if (cmdq->item == NULL) {
-		cmdq->cmd = NULL;
-		cmdq_continue(cmdq);
-	}
-}
-
-/* Add command list to queue. */
-void
-cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
-{
-	struct cmd_q_item	*item;
-
-	item = xcalloc(1, sizeof *item);
-	item->cmdlist = cmdlist;
-	TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry);
-	cmdlist->references++;
-
-	if (m != NULL)
-		memcpy(&item->mouse, m, sizeof item->mouse);
-	else
-		item->mouse.valid = 0;
-}
-
-/* Process one command. */
-static enum cmd_retval
-cmdq_continue_one(struct cmd_q *cmdq)
-{
-	struct cmd_list		*cmdlist = cmdq->item->cmdlist;
-	struct cmd		*cmd = cmdq->cmd;
-	enum cmd_retval		 retval;
-	char			*tmp;
-	int			 flags = !!(cmd->flags & CMD_CONTROL);
-	const char		*name;
-	struct cmd_find_state	*fsp, fs;
-
-	cmdlist->references++;
-
-	tmp = cmd_print(cmd);
-	log_debug("cmdq %p: %s", cmdq, tmp);
-	free(tmp);
-
-	cmdq->time = time(NULL);
-	cmdq->number++;
-
-	cmdq_guard(cmdq, "begin", flags);
-
-	if (cmd_prepare_state(cmd, cmdq, cmdq->parent) != 0)
-		goto error;
-
-	retval = cmd->entry->exec(cmd, cmdq);
-	if (retval == CMD_RETURN_ERROR)
-		goto error;
-
-	if (~cmd->entry->flags & CMD_AFTERHOOK)
-		goto end;
-
-	if (cmd_find_valid_state(&cmdq->state.tflag))
-		fsp = &cmdq->state.tflag;
-	else {
-		if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
-			goto end;
-		fsp = &fs;
-	}
-	name = cmd->entry->name;
-	if (hooks_wait(fsp->s->hooks, cmdq, fsp, "after-%s", name) == 0)
-		retval = CMD_RETURN_WAIT;
-
-end:
-	cmdq_guard(cmdq, "end", flags);
-	cmd_list_free(cmdlist);
-	return (retval);
-
-error:
-	cmdq_guard(cmdq, "error", flags);
-	cmd_list_free(cmdlist);
-	return (CMD_RETURN_ERROR);
-}
-
-/* Continue processing command queue. Returns 1 if finishes empty. */
-int
-cmdq_continue(struct cmd_q *cmdq)
-{
-	struct client		*c = cmdq->client;
-	struct cmd_q_item	*next;
-	enum cmd_retval		 retval;
-	int			 empty;
-
-	log_debug("continuing cmdq %p: flags %#x (%p)", cmdq, cmdq->flags, c);
-	cmdq->references++;
-
-	empty = TAILQ_EMPTY(&cmdq->queue);
-	if (empty)
-		goto empty;
-
-	if (cmdq->item == NULL) {
-		cmdq->item = TAILQ_FIRST(&cmdq->queue);
-		cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
-	} else
-		cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
-
-	do {
-		while (cmdq->cmd != NULL) {
-			retval = cmdq_continue_one(cmdq);
-			if (retval == CMD_RETURN_ERROR)
-				break;
-			if (retval == CMD_RETURN_WAIT)
-				goto out;
-			if (retval == CMD_RETURN_STOP) {
-				cmdq_flush(cmdq);
-				goto empty;
-			}
-			cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
-		}
-		next = TAILQ_NEXT(cmdq->item, qentry);
-
-		TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry);
-		cmd_list_free(cmdq->item->cmdlist);
-		free(cmdq->item);
-
-		cmdq->item = next;
-		if (cmdq->item != NULL)
-			cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
-	} while (cmdq->item != NULL);
-
-empty:
-	log_debug("cmdq %p empty", cmdq);
-	if (cmdq->client_exit > 0)
-		cmdq->client->flags |= CLIENT_EXIT;
-	if (cmdq->emptyfn != NULL)
-		cmdq->emptyfn(cmdq);
-	empty = 1;
-
-out:
-	cmdq_free(cmdq);
-	return (empty);
-}
-
-/* Flush command queue. */
-static void
-cmdq_flush(struct cmd_q *cmdq)
-{
-	struct cmd_q_item	*item, *item1;
-
-	TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) {
-		TAILQ_REMOVE(&cmdq->queue, item, qentry);
-		cmd_list_free(item->cmdlist);
-		free(item);
-	}
-	cmdq->item = NULL;
-}
-
diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c
index be652b56..3360bd6b 100644
--- a/cmd-resize-pane.c
+++ b/cmd-resize-pane.c
@@ -60,12 +60,12 @@ cmd_resize_pane_exec(struct cmd *self, struct cmd_q *cmdq)
 	int			 x, y;
 
 	if (args_has(args, 'M')) {
-		if (cmd_mouse_window(&cmdq->item->mouse, &s) == NULL)
+		if (cmd_mouse_window(&cmdq->mouse, &s) == NULL)
 			return (CMD_RETURN_NORMAL);
 		if (c == NULL || c->session != s)
 			return (CMD_RETURN_NORMAL);
 		c->tty.mouse_drag_update = cmd_resize_pane_mouse_update;
-		cmd_resize_pane_mouse_update(c, &cmdq->item->mouse);
+		cmd_resize_pane_mouse_update(c, &cmdq->mouse);
 		return (CMD_RETURN_NORMAL);
 	}
 
diff --git a/cmd-run-shell.c b/cmd-run-shell.c
index c81c76f8..ad2d4c2f 100644
--- a/cmd-run-shell.c
+++ b/cmd-run-shell.c
@@ -51,7 +51,6 @@ const struct cmd_entry cmd_run_shell_entry = {
 struct cmd_run_shell_data {
 	char		*cmd;
 	struct cmd_q	*cmdq;
-	int		 bflag;
 	int		 wp_id;
 };
 
@@ -92,6 +91,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 		cwd = s->cwd;
 	else
 		cwd = NULL;
+
 	ft = format_create(cmdq, 0);
 	format_defaults(ft, cmdq->state.c, s, wl, wp);
 	shellcmd = format_expand(ft, args->argv[0]);
@@ -99,20 +99,24 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_q *cmdq)
 
 	cdata = xcalloc(1, sizeof *cdata);
 	cdata->cmd = shellcmd;
-	cdata->bflag = args_has(args, 'b');
 
 	if (args_has(args, 't') && wp != NULL)
 		cdata->wp_id = wp->id;
 	else
 		cdata->wp_id = -1;
 
-	cdata->cmdq = cmdq;
-	cmdq->references++;
+	if (args_has(args, 't') && wp != NULL)
+		cdata->wp_id = wp->id;
+	else
+		cdata->wp_id = -1;
+
+	if (!args_has(args, 'b'))
+		cdata->cmdq = cmdq;
 
 	job_run(shellcmd, s, cwd, cmd_run_shell_callback, cmd_run_shell_free,
 	    cdata);
 
-	if (cdata->bflag)
+	if (args_has(args, 'b'))
 		return (CMD_RETURN_NORMAL);
 	return (CMD_RETURN_WAIT);
 }
@@ -121,16 +125,11 @@ static void
 cmd_run_shell_callback(struct job *job)
 {
 	struct cmd_run_shell_data	*cdata = job->data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-	char				*cmd, *msg, *line;
+	char				*cmd = cdata->cmd, *msg, *line;
 	size_t				 size;
 	int				 retcode;
 	u_int				 lines;
 
-	if (cmdq->flags & CMD_Q_DEAD)
-		return;
-	cmd = cdata->cmd;
-
 	lines = 0;
 	do {
 		if ((line = evbuffer_readline(job->event->input)) != NULL) {
@@ -163,16 +162,15 @@ cmd_run_shell_callback(struct job *job)
 	if (msg != NULL)
 		cmd_run_shell_print(job, msg);
 	free(msg);
+
+	if (cdata->cmdq != NULL)
+		cdata->cmdq->flags &= ~CMD_Q_WAITING;
 }
 
 static void
 cmd_run_shell_free(void *data)
 {
 	struct cmd_run_shell_data	*cdata = data;
-	struct cmd_q			*cmdq = cdata->cmdq;
-
-	if (!cmdq_free(cmdq) && !cdata->bflag)
-		cmdq_continue(cmdq);
 
 	free(cdata->cmd);
 	free(cdata);
diff --git a/cmd-send-keys.c b/cmd-send-keys.c
index 062a13d7..94316834 100644
--- a/cmd-send-keys.c
+++ b/cmd-send-keys.c
@@ -62,7 +62,7 @@ cmd_send_keys_exec(struct cmd *self, struct cmd_q *cmdq)
 	struct client		*c = cmdq->state.c;
 	struct window_pane	*wp = cmdq->state.tflag.wp;
 	struct session		*s = cmdq->state.tflag.s;
-	struct mouse_event	*m = &cmdq->item->mouse;
+	struct mouse_event	*m = &cmdq->mouse;
 	const u_char		*keystr;
 	int			 i, literal;
 	key_code		 key;
diff --git a/cmd-source-file.c b/cmd-source-file.c
index 336a79e6..6f9c4451 100644
--- a/cmd-source-file.c
+++ b/cmd-source-file.c
@@ -28,7 +28,7 @@
 
 static enum cmd_retval	cmd_source_file_exec(struct cmd *, struct cmd_q *);
 
-static void		cmd_source_file_done(struct cmd_q *);
+static enum cmd_retval	cmd_source_file_done(struct cmd_q *, void *);
 
 const struct cmd_entry cmd_source_file_entry = {
 	.name = "source-file",
@@ -45,53 +45,31 @@ static enum cmd_retval
 cmd_source_file_exec(struct cmd *self, struct cmd_q *cmdq)
 {
 	struct args	*args = self->args;
-	struct cmd_q	*cmdq1;
+	struct client	*c = cmdq->client;
 	int		 quiet;
-
-	cmdq1 = cmdq_new(cmdq->client);
-	cmdq1->emptyfn = cmd_source_file_done;
-	cmdq1->data = cmdq;
+	struct cmd_q	*new_cmdq;
 
 	quiet = args_has(args, 'q');
-	switch (load_cfg(args->argv[0], cmdq1, quiet)) {
+	switch (load_cfg(args->argv[0], c, cmdq, quiet)) {
 	case -1:
-		cmdq_free(cmdq1);
-		if (cfg_references == 0) {
+		if (cfg_finished)
 			cfg_print_causes(cmdq);
-			return (CMD_RETURN_ERROR);
-		}
-		return (CMD_RETURN_NORMAL);
+		return (CMD_RETURN_ERROR);
 	case 0:
-		cmdq_free(cmdq1);
-		if (cfg_references == 0)
+		if (cfg_finished)
 			cfg_print_causes(cmdq);
 		return (CMD_RETURN_NORMAL);
 	}
-
-	log_debug("%s: cmdq %p, parent %p", __func__, cmdq1, cmdq);
-
-	cmdq->references++;
-	cfg_references++;
-
-	cmdq_continue(cmdq1);
-	return (CMD_RETURN_WAIT);
+	if (cfg_finished) {
+		new_cmdq = cmdq_get_callback(cmd_source_file_done, NULL);
+		cmdq_insert_after(cmdq, new_cmdq);
+	}
+	return (CMD_RETURN_NORMAL);
 }
 
-static void
-cmd_source_file_done(struct cmd_q *cmdq1)
+static enum cmd_retval
+cmd_source_file_done(struct cmd_q *cmdq, __unused void *data)
 {
-	struct cmd_q	*cmdq = cmdq1->data;
-
-	log_debug("%s: cmdq %p, parent %p", __func__, cmdq1, cmdq);
-
-	if (cmdq1->client_exit >= 0)
-		cmdq->client_exit = cmdq1->client_exit;
-	cmdq_free(cmdq1);
-
-	cfg_references--;
-	if (cmdq_free(cmdq))
-		return;
-	if (cfg_references == 0)
-		cfg_print_causes(cmdq);
-	cmdq_continue(cmdq);
+	cfg_print_causes(cmdq);
+	return (CMD_RETURN_NORMAL);
 }
diff --git a/cmd-split-window.c b/cmd-split-window.c
index 41a53c63..ec2ea8cc 100644
--- a/cmd-split-window.c
+++ b/cmd-split-window.c
@@ -188,8 +188,8 @@ cmd_split_window_exec(struct cmd *self, struct cmd_q *cmdq)
 	fs.w = w;
 	fs.wp = new_wp;
 	cmd_find_log_state(__func__, &fs);
-	if (hooks_wait(s->hooks, cmdq, &fs, "after-split-window") == 0)
-		return (CMD_RETURN_WAIT);
+	hooks_insert(s->hooks, cmdq, &fs, "after-split-window");
+
 	return (CMD_RETURN_NORMAL);
 
 error:
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
index fb2fb699..505e4d63 100644
--- a/cmd-wait-for.c
+++ b/cmd-wait-for.c
@@ -41,13 +41,18 @@ const struct cmd_entry cmd_wait_for_entry = {
 	.exec = cmd_wait_for_exec
 };
 
+struct wait_item {
+	struct cmd_q		*cmdq;
+	TAILQ_ENTRY(wait_item)	 entry;
+};
+
 struct wait_channel {
 	const char	       *name;
 	int			locked;
 	int			woken;
 
-	TAILQ_HEAD(, cmd_q)	waiters;
-	TAILQ_HEAD(, cmd_q)	lockers;
+	TAILQ_HEAD(, wait_item)	waiters;
+	TAILQ_HEAD(, wait_item)	lockers;
 
 	RB_ENTRY(wait_channel)	entry;
 };
@@ -135,7 +140,7 @@ static enum cmd_retval
 cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
     struct wait_channel *wc)
 {
-	struct cmd_q	*wq, *wq1;
+	struct wait_item	*wi, *wi1;
 
 	if (wc == NULL)
 		wc = cmd_wait_for_add(name);
@@ -147,10 +152,11 @@ cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
 	}
 	log_debug("signal wait channel %s, with waiters", wc->name);
 
-	TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
-		TAILQ_REMOVE(&wc->waiters, wq, waitentry);
-		if (!cmdq_free(wq))
-			cmdq_continue(wq);
+	TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+		wi->cmdq->flags &= ~CMD_Q_WAITING;
+
+		TAILQ_REMOVE(&wc->waiters, wi, entry);
+		free(wi);
 	}
 
 	cmd_wait_for_remove(wc);
@@ -158,10 +164,10 @@ cmd_wait_for_signal(__unused struct cmd_q *cmdq, const char *name,
 }
 
 static enum cmd_retval
-cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
-    struct wait_channel *wc)
+cmd_wait_for_wait(struct cmd_q *cmdq, const char *name, struct wait_channel *wc)
 {
-	struct client	*c = cmdq->client;
+	struct client		*c = cmdq->client;
+	struct wait_item	*wi;
 
 	if (c == NULL || c->session != NULL) {
 		cmdq_error(cmdq, "not able to wait");
@@ -178,16 +184,18 @@ cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
 	}
 	log_debug("wait channel %s not woken (%p)", wc->name, c);
 
-	TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
-	cmdq->references++;
+	wi = xcalloc(1, sizeof *wi);
+	wi->cmdq = cmdq;
+	TAILQ_INSERT_TAIL(&wc->waiters, wi, entry);
 
 	return (CMD_RETURN_WAIT);
 }
 
 static enum cmd_retval
-cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
-    struct wait_channel *wc)
+cmd_wait_for_lock(struct cmd_q *cmdq, const char *name, struct wait_channel *wc)
 {
+	struct wait_item	*wi;
+
 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
 		cmdq_error(cmdq, "not able to lock");
 		return (CMD_RETURN_ERROR);
@@ -197,8 +205,9 @@ cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
 		wc = cmd_wait_for_add(name);
 
 	if (wc->locked) {
-		TAILQ_INSERT_TAIL(&wc->lockers, cmdq, waitentry);
-		cmdq->references++;
+		wi = xcalloc(1, sizeof *wi);
+		wi->cmdq = cmdq;
+		TAILQ_INSERT_TAIL(&wc->lockers, wi, entry);
 		return (CMD_RETURN_WAIT);
 	}
 	wc->locked = 1;
@@ -210,17 +219,17 @@ static enum cmd_retval
 cmd_wait_for_unlock(struct cmd_q *cmdq, const char *name,
     struct wait_channel *wc)
 {
-	struct cmd_q	*wq;
+	struct wait_item	*wi;
 
 	if (wc == NULL || !wc->locked) {
 		cmdq_error(cmdq, "channel %s not locked", name);
 		return (CMD_RETURN_ERROR);
 	}
 
-	if ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) {
-		TAILQ_REMOVE(&wc->lockers, wq, waitentry);
-		if (!cmdq_free(wq))
-			cmdq_continue(wq);
+	if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) {
+		wi->cmdq->flags &= ~CMD_Q_WAITING;
+		TAILQ_REMOVE(&wc->lockers, wi, entry);
+		free(wi);
 	} else {
 		wc->locked = 0;
 		cmd_wait_for_remove(wc);
@@ -233,19 +242,19 @@ void
 cmd_wait_for_flush(void)
 {
 	struct wait_channel	*wc, *wc1;
-	struct cmd_q		*wq, *wq1;
+	struct wait_item	*wi, *wi1;
 
 	RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
-		TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
-			TAILQ_REMOVE(&wc->waiters, wq, waitentry);
-			if (!cmdq_free(wq))
-				cmdq_continue(wq);
+		TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
+			wi->cmdq->flags &= ~CMD_Q_WAITING;
+			TAILQ_REMOVE(&wc->waiters, wi, entry);
+			free(wi);
 		}
 		wc->woken = 1;
-		TAILQ_FOREACH_SAFE(wq, &wc->lockers, waitentry, wq1) {
-			TAILQ_REMOVE(&wc->lockers, wq, waitentry);
-			if (!cmdq_free(wq))
-				cmdq_continue(wq);
+		TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
+			wi->cmdq->flags &= ~CMD_Q_WAITING;
+			TAILQ_REMOVE(&wc->lockers, wi, entry);
+			free(wi);
 		}
 		wc->locked = 0;
 		cmd_wait_for_remove(wc);
diff --git a/cmd.c b/cmd.c
index 7964f654..9c37b849 100644
--- a/cmd.c
+++ b/cmd.c
@@ -390,12 +390,11 @@ usage:
 
 static int
 cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
-    struct cmd_q *cmdq, struct cmd_q *parent)
+    struct cmd_q *cmdq)
 {
 	int			 targetflags, error;
 	struct cmd_find_state	*fs = NULL;
-	struct cmd_find_state	*current = NULL;
-	struct cmd_find_state	 tmp;
+	struct cmd_find_state	 current;
 
 	if (flag == CMD_NONE ||
 	    flag == CMD_CLIENT ||
@@ -449,21 +448,12 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	default:
 		fatalx("unknown %cflag %d", c, flag);
 	}
-
 	log_debug("%s: flag %c %d %#x", __func__, c, flag, targetflags);
-	if (parent != NULL) {
-		if (c == 't')
-			current = &parent->state.tflag;
-		else if (c == 's')
-			current = &parent->state.sflag;
-	}
-	if (current == NULL || !cmd_find_valid_state(current)) {
-		error = cmd_find_current(&tmp, cmdq, targetflags);
-		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
-			return (-1);
-		current = &tmp;
-	}
-	if (!cmd_find_empty_state(current) && !cmd_find_valid_state(current))
+
+	error = cmd_find_current(&current, cmdq, targetflags);
+	if (error != 0 && ~targetflags & CMD_FIND_QUIET)
+		return (-1);
+	if (!cmd_find_empty_state(&current) && !cmd_find_valid_state(&current))
 		fatalx("invalid current state");
 
 	switch (flag) {
@@ -475,13 +465,13 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_SESSION_CANFAIL:
 	case CMD_SESSION_PREFERUNATTACHED:
 	case CMD_SESSION_WITHPANE:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_SESSION, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
 		break;
 	case CMD_MOVEW_R:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_SESSION, CMD_FIND_QUIET);
 		if (error == 0)
 			break;
@@ -490,7 +480,7 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_WINDOW_CANFAIL:
 	case CMD_WINDOW_MARKED:
 	case CMD_WINDOW_INDEX:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_WINDOW, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
@@ -498,7 +488,7 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 	case CMD_PANE:
 	case CMD_PANE_CANFAIL:
 	case CMD_PANE_MARKED:
-		error = cmd_find_target(fs, current, cmdq, target,
+		error = cmd_find_target(fs, &current, cmdq, target,
 		    CMD_FIND_PANE, targetflags);
 		if (error != 0 && ~targetflags & CMD_FIND_QUIET)
 			return (-1);
@@ -510,14 +500,14 @@ cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
 }
 
 int
-cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq, struct cmd_q *parent)
+cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq)
 {
-	const struct cmd_entry		*entry = cmd->entry;
-	struct cmd_state		*state = &cmdq->state;
-	char				*tmp;
-	enum cmd_entry_flag		 flag;
-	const char			*s;
-	int				 error;
+	const struct cmd_entry	*entry = cmd->entry;
+	struct cmd_state	*state = &cmdq->state;
+	char			*tmp;
+	enum cmd_entry_flag	 flag;
+	const char		*s;
+	int			 error;
 
 	tmp = cmd_print(cmd);
 	log_debug("preparing state for %s (client %p)", tmp, cmdq->client);
@@ -546,18 +536,19 @@ cmd_prepare_state(struct cmd *cmd, struct cmd_q *cmdq, struct cmd_q *parent)
 		state->c = cmd_find_client(cmdq, s, 1);
 		break;
 	}
+	log_debug("using client %p", state->c);
 
 	s = args_get(cmd->args, 't');
 	log_debug("preparing -t state: target %s", s == NULL ? "none" : s);
 
-	error = cmd_prepare_state_flag('t', s, entry->tflag, cmdq, parent);
+	error = cmd_prepare_state_flag('t', s, entry->tflag, cmdq);
 	if (error != 0)
 		return (error);
 
 	s = args_get(cmd->args, 's');
 	log_debug("preparing -s state: target %s", s == NULL ? "none" : s);
 
-	error = cmd_prepare_state_flag('s', s, entry->sflag, cmdq, parent);
+	error = cmd_prepare_state_flag('s', s, entry->sflag, cmdq);
 	if (error != 0)
 		return (error);
 
diff --git a/control.c b/control.c
index f6dedca8..cd605f4f 100644
--- a/control.c
+++ b/control.c
@@ -49,6 +49,21 @@ control_write_buffer(struct client *c, struct evbuffer *buffer)
 	server_client_push_stdout(c);
 }
 
+/* Control error callback. */
+static enum cmd_retval
+control_error(struct cmd_q *cmdq, void *data)
+{
+	struct client	*c = cmdq->client;
+	char		*error = data;
+
+	cmdq_guard(cmdq, "begin", 1);
+	control_write(c, "parse error: %s", error);
+	cmdq_guard(cmdq, "error", 1);
+
+	free(error);
+	return (CMD_RETURN_NORMAL);
+}
+
 /* Control input callback. Read lines and fire commands. */
 void
 control_callback(struct client *c, int closed, __unused void *data)
@@ -56,6 +71,7 @@ control_callback(struct client *c, int closed, __unused void *data)
 	char		*line, *cause;
 	struct cmd_list	*cmdlist;
 	struct cmd	*cmd;
+	struct cmd_q	*cmdq;
 
 	if (closed)
 		c->flags |= CLIENT_EXIT;
@@ -70,18 +86,13 @@ control_callback(struct client *c, int closed, __unused void *data)
 		}
 
 		if (cmd_string_parse(line, &cmdlist, NULL, 0, &cause) != 0) {
-			c->cmdq->time = time(NULL);
-			c->cmdq->number++;
-
-			cmdq_guard(c->cmdq, "begin", 1);
-			control_write(c, "parse error: %s", cause);
-			cmdq_guard(c->cmdq, "error", 1);
-
-			free(cause);
+			cmdq = cmdq_get_callback(control_error, cause);
+			cmdq_append(c, cmdq);
 		} else {
 			TAILQ_FOREACH(cmd, &cmdlist->list, qentry)
 				cmd->flags |= CMD_CONTROL;
-			cmdq_run(c->cmdq, cmdlist, NULL);
+			cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+			cmdq_append(c, cmdq);
 			cmd_list_free(cmdlist);
 		}
 
diff --git a/format.c b/format.c
index ed6f2cab..f9128f01 100644
--- a/format.c
+++ b/format.c
@@ -473,7 +473,6 @@ struct format_tree *
 format_create(struct cmd_q *cmdq, int flags)
 {
 	struct format_tree	*ft;
-	struct cmd		*cmd;
 
 	if (!event_initialized(&format_job_event)) {
 		evtimer_set(&format_job_event, format_job_timer, NULL);
@@ -491,11 +490,9 @@ format_create(struct cmd_q *cmdq, int flags)
 	format_add_tv(ft, "start_time", &start_time);
 
 	if (cmdq != NULL && cmdq->cmd != NULL)
-		format_add(ft, "command_name", "%s", cmdq->cmd->entry->name);
-	if (cmdq != NULL && cmdq->parent != NULL) {
-		cmd = cmdq->parent->cmd;
-		format_add(ft, "command_hooked", "%s", cmd->entry->name);
-	}
+		format_add(ft, "command", "%s", cmdq->cmd->entry->name);
+	if (cmdq != NULL && cmdq->hook != NULL)
+		format_add(ft, "hook", "%s", cmdq->hook);
 
 	return (ft);
 }
diff --git a/hooks.c b/hooks.c
index 2e0126b7..cdd2cf45 100644
--- a/hooks.c
+++ b/hooks.c
@@ -33,7 +33,6 @@ RB_GENERATE_STATIC(hooks_tree, hook, entry, hooks_cmp);
 
 static struct hook	*hooks_find1(struct hooks *, const char *);
 static void		 hooks_free1(struct hooks *, struct hook *);
-static void		 hooks_emptyfn(struct cmd_q *);
 
 static int
 hooks_cmp(struct hook *hook1, struct hook *hook2)
@@ -140,28 +139,14 @@ hooks_find(struct hooks *hooks, const char *name)
 	return (hook);
 }
 
-static void
-hooks_emptyfn(struct cmd_q *hooks_cmdq)
-{
-	struct cmd_q	*cmdq = hooks_cmdq->data;
-
-	if (cmdq != NULL) {
-		if (hooks_cmdq->client_exit >= 0)
-			cmdq->client_exit = hooks_cmdq->client_exit;
-		if (!cmdq_free(cmdq))
-			cmdq_continue(cmdq);
-	}
-	cmdq_free(hooks_cmdq);
-}
-
-int
+void
 hooks_run(struct hooks *hooks, struct client *c, struct cmd_find_state *fs,
     const char *fmt, ...)
 {
 	struct hook	*hook;
-	struct cmd_q	*hooks_cmdq;
 	va_list		 ap;
 	char		*name;
+	struct cmd_q	*new_cmdq, *loop;
 
 	va_start(ap, fmt);
 	xvasprintf(&name, fmt, ap);
@@ -170,34 +155,30 @@ hooks_run(struct hooks *hooks, struct client *c, struct cmd_find_state *fs,
 	hook = hooks_find(hooks, name);
 	if (hook == NULL) {
 		free(name);
-		return (-1);
+		return;
 	}
 	log_debug("running hook %s", name);
+
+	new_cmdq = cmdq_get_command(hook->cmdlist, fs, NULL, CMD_Q_NOHOOKS);
+
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 	free(name);
 
-	hooks_cmdq = cmdq_new(c);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
-
-	if (fs != NULL)
-		cmd_find_copy_state(&hooks_cmdq->current, fs);
-	hooks_cmdq->parent = NULL;
-
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	cmdq_free(hooks_cmdq);
-	return (0);
+	cmdq_append(c, new_cmdq);
 }
 
-int
-hooks_wait(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
+void
+hooks_insert(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
     const char *fmt, ...)
 {
 	struct hook	*hook;
-	struct cmd_q	*hooks_cmdq;
 	va_list		 ap;
 	char		*name;
+	struct cmd_q	*new_cmdq, *loop;
 
 	if (cmdq->flags & CMD_Q_NOHOOKS)
-		return (-1);
+		return;
 
 	va_start(ap, fmt);
 	xvasprintf(&name, fmt, ap);
@@ -206,23 +187,18 @@ hooks_wait(struct hooks *hooks, struct cmd_q *cmdq, struct cmd_find_state *fs,
 	hook = hooks_find(hooks, name);
 	if (hook == NULL) {
 		free(name);
-		return (-1);
+		return;
 	}
 	log_debug("running hook %s (parent %p)", name, cmdq);
+
+	new_cmdq = cmdq_get_command(hook->cmdlist, fs, NULL, CMD_Q_NOHOOKS);
+
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 	free(name);
 
-	hooks_cmdq = cmdq_new(cmdq->client);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
-
-	if (fs != NULL)
-		cmd_find_copy_state(&hooks_cmdq->current, fs);
-	hooks_cmdq->parent = cmdq;
-
-	hooks_cmdq->emptyfn = hooks_emptyfn;
-	hooks_cmdq->data = cmdq;
-
 	if (cmdq != NULL)
-		cmdq->references++;
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	return (0);
+		cmdq_insert_after(cmdq, new_cmdq);
+	else
+		cmdq_append(NULL, new_cmdq);
 }
diff --git a/key-bindings.c b/key-bindings.c
index b464c199..af53720c 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -377,18 +377,22 @@ key_bindings_init(void)
 	struct cmd_list	*cmdlist;
 	char		*cause;
 	int		 error;
-	struct cmd_q	*cmdq;
 
-	cmdq = cmdq_new(NULL);
 	for (i = 0; i < nitems(defaults); i++) {
 		error = cmd_string_parse(defaults[i], &cmdlist,
 		    "<default-keys>", i, &cause);
 		if (error != 0)
 			fatalx("bad default key");
-		cmdq_run(cmdq, cmdlist, NULL);
+		cmdq_append(NULL, cmdq_get_command(cmdlist, NULL, NULL, 0));
 		cmd_list_free(cmdlist);
 	}
-	cmdq_free(cmdq);
+}
+
+static enum cmd_retval
+key_bindings_read_only(struct cmd_q *cmdq, __unused void *data)
+{
+	cmdq_error(cmdq, "client is read-only");
+	return (CMD_RETURN_ERROR);
 }
 
 void
@@ -403,10 +407,8 @@ key_bindings_dispatch(struct key_binding *bd, struct client *c,
 		if (!(cmd->entry->flags & CMD_READONLY))
 			readonly = 0;
 	}
-	if (!readonly && (c->flags & CLIENT_READONLY)) {
-		cmdq_error(c->cmdq, "client is read-only");
-		return;
-	}
-
-	cmdq_run(c->cmdq, bd->cmdlist, m);
+	if (!readonly && (c->flags & CLIENT_READONLY))
+		cmdq_append(c, cmdq_get_callback(key_bindings_read_only, NULL));
+	else
+		cmdq_append(c, cmdq_get_command(bd->cmdlist, NULL, m, 0));
 }
diff --git a/notify.c b/notify.c
index 416b81f6..a75b3182 100644
--- a/notify.c
+++ b/notify.c
@@ -66,7 +66,7 @@ notify_hook(struct notify_entry *ne)
 	const char		*name;
 	struct cmd_find_state	 fs;
 	struct hook		*hook;
-	struct cmd_q		*hooks_cmdq;
+	struct cmd_q		*new_cmdq, *loop;
 
 	name = notify_hooks[ne->type];
 	if (name == NULL)
@@ -87,14 +87,12 @@ notify_hook(struct notify_entry *ne)
 		return;
 	log_debug("notify hook %s", name);
 
-	hooks_cmdq = cmdq_new(NULL);
-	hooks_cmdq->flags |= CMD_Q_NOHOOKS;
+	new_cmdq = cmdq_get_command(hook->cmdlist, &fs, NULL, CMD_Q_NOHOOKS);
 
-	cmd_find_copy_state(&hooks_cmdq->current, &fs);
-	hooks_cmdq->parent = NULL;
+	for (loop = new_cmdq; loop != NULL; loop = loop->next)
+		loop->hook = xstrdup(name);
 
-	cmdq_run(hooks_cmdq, hook->cmdlist, NULL);
-	cmdq_free(hooks_cmdq);
+	cmdq_append(NULL, new_cmdq);
 }
 
 static void
diff --git a/server-client.c b/server-client.c
index 7e4d1ee4..7ad87cd1 100644
--- a/server-client.c
+++ b/server-client.c
@@ -126,8 +126,7 @@ server_client_create(int fd)
 	c->fd = -1;
 	c->cwd = NULL;
 
-	c->cmdq = cmdq_new(c);
-	c->cmdq->client_exit = 1;
+	TAILQ_INIT(&c->queue);
 
 	c->stdin_data = evbuffer_new();
 	c->stdout_data = evbuffer_new();
@@ -244,10 +243,6 @@ server_client_lost(struct client *c)
 	free(c->prompt_string);
 	free(c->prompt_buffer);
 
-	c->cmdq->flags |= CMD_Q_DEAD;
-	cmdq_free(c->cmdq);
-	c->cmdq = NULL;
-
 	environ_free(c->environ);
 
 	proc_remove_peer(c->peer);
@@ -281,6 +276,9 @@ server_client_free(__unused int fd, __unused short events, void *arg)
 
 	log_debug("free client %p (%d references)", c, c->references);
 
+	if (!TAILQ_EMPTY(&c->queue))
+		fatalx("queue not empty");
+
 	if (c->references == 0)
 		free(c);
 }
@@ -1254,6 +1252,29 @@ server_client_dispatch(struct imsg *imsg, void *arg)
 	}
 }
 
+/* Callback when command is done. */
+static enum cmd_retval
+server_client_command_done(struct cmd_q *cmdq, __unused void *data)
+{
+	struct client	*c = cmdq->client;
+
+	if (~c->flags & CLIENT_ATTACHED)
+		c->flags |= CLIENT_EXIT;
+	return (CMD_RETURN_NORMAL);
+}
+
+/* Show an error message. */
+static enum cmd_retval
+server_client_command_error(struct cmd_q *cmdq, void *data)
+{
+	char	*error = data;
+
+	cmdq_error(cmdq, "%s", error);
+	free(error);
+
+	return (CMD_RETURN_NORMAL);
+}
+
 /* Handle command message. */
 static void
 server_client_dispatch_command(struct client *c, struct imsg *imsg)
@@ -1276,7 +1297,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 
 	argc = data.argc;
 	if (cmd_unpack_argv(buf, len, argc, &argv) != 0) {
-		cmdq_error(c->cmdq, "command too long");
+		cause = xstrdup("command too long");
 		goto error;
 	}
 
@@ -1287,20 +1308,19 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 	}
 
 	if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) {
-		cmdq_error(c->cmdq, "%s", cause);
 		cmd_free_argv(argc, argv);
 		goto error;
 	}
 	cmd_free_argv(argc, argv);
 
-	if (c != cfg_client || cfg_finished)
-		cmdq_run(c->cmdq, cmdlist, NULL);
-	else
-		cmdq_append(c->cmdq, cmdlist, NULL);
+	cmdq_append(c, cmdq_get_command(cmdlist, NULL, NULL, 0));
+	cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL));
 	cmd_list_free(cmdlist);
 	return;
 
 error:
+	cmdq_append(c, cmdq_get_callback(server_client_command_error, cause));
+
 	if (cmdlist != NULL)
 		cmd_list_free(cmdlist);
 
diff --git a/server.c b/server.c
index e6d1c09f..15fd5546 100644
--- a/server.c
+++ b/server.c
@@ -190,9 +190,17 @@ static int
 server_loop(void)
 {
 	struct client	*c;
+	u_int		 items;
+
+	notify_drain();
+
+	do {
+		items = cmdq_next(NULL);
+		TAILQ_FOREACH(c, &clients, entry)
+		    items += cmdq_next(c);
+	} while (items != 0);
 
 	server_client_loop();
-	notify_drain();
 
 	if (!options_get_number(global_options, "exit-unattached")) {
 		if (!RB_EMPTY(&sessions))
diff --git a/tmux.1 b/tmux.1
index a07b5e6c..88697bcf 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3456,8 +3456,7 @@ The following variables are available, where appropriate:
 .It Li "client_tty" Ta "" Ta "Pseudo terminal of client"
 .It Li "client_utf8" Ta "" Ta "1 if client supports utf8"
 .It Li "client_width" Ta "" Ta "Width of client"
-.It Li "command_hooked" Ta "" Ta "Name of command hooked, if any"
-.It Li "command_name" Ta "" Ta "Name of command in use, if any"
+.It Li "command" Ta "" Ta "Name of command in use, if any"
 .It Li "command_list_name" Ta "" Ta "Command name if listing commands"
 .It Li "command_list_alias" Ta "" Ta "Command alias if listing commands"
 .It Li "command_list_usage" Ta "" Ta "Command usage if listing commands"
@@ -3467,6 +3466,7 @@ The following variables are available, where appropriate:
 .It Li "history_bytes" Ta "" Ta "Number of bytes in window history"
 .It Li "history_limit" Ta "" Ta "Maximum window history lines"
 .It Li "history_size" Ta "" Ta "Size of history in bytes"
+.It Li "hook" Ta "" Ta "Name of running hook, if any"
 .It Li "host" Ta "#H" Ta "Hostname of local host"
 .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"
 .It Li "insert_flag" Ta "" Ta "Pane insert flag"
diff --git a/tmux.h b/tmux.h
index bbb345b3..b5e5d96c 100644
--- a/tmux.h
+++ b/tmux.h
@@ -40,6 +40,8 @@ extern char   **environ;
 
 struct args;
 struct client;
+struct cmd_q;
+struct cmd_q_list;
 struct environ;
 struct input_ctx;
 struct mode_key_cmdstr;
@@ -631,7 +633,6 @@ struct grid {
 struct hook {
 	const char	*name;
 
-	struct cmd_q	*cmdq;
 	struct cmd_list	*cmdlist;
 
 	RB_ENTRY(hook)	 entry;
@@ -1160,101 +1161,6 @@ struct message_entry {
 	TAILQ_ENTRY(message_entry) entry;
 };
 
-/* Client connection. */
-struct client {
-	struct tmuxpeer	*peer;
-
-	pid_t		 pid;
-	int		 fd;
-	struct event	 event;
-	int		 retval;
-
-	struct timeval	 creation_time;
-	struct timeval	 activity_time;
-
-	struct environ	*environ;
-
-	char		*title;
-	const char	*cwd;
-
-	char		*term;
-	char		*ttyname;
-	struct tty	 tty;
-
-	void		(*stdin_callback)(struct client *, int, void *);
-	void		*stdin_callback_data;
-	struct evbuffer	*stdin_data;
-	int		 stdin_closed;
-	struct evbuffer	*stdout_data;
-	struct evbuffer	*stderr_data;
-
-	struct event	 repeat_timer;
-
-	struct event	 click_timer;
-	u_int		 click_button;
-
-	struct event	 status_timer;
-	struct screen	 status;
-
-#define CLIENT_TERMINAL 0x1
-#define CLIENT_LOGIN 0x2
-#define CLIENT_EXIT 0x4
-#define CLIENT_REDRAW 0x8
-#define CLIENT_STATUS 0x10
-#define CLIENT_REPEAT 0x20
-#define CLIENT_SUSPENDED 0x40
-/* 0x80 unused */
-#define CLIENT_IDENTIFY 0x100
-#define CLIENT_DEAD 0x200
-#define CLIENT_BORDERS 0x400
-#define CLIENT_READONLY 0x800
-#define CLIENT_REDRAWWINDOW 0x1000
-#define CLIENT_CONTROL 0x2000
-#define CLIENT_CONTROLCONTROL 0x4000
-#define CLIENT_FOCUSED 0x8000
-#define CLIENT_UTF8 0x10000
-#define CLIENT_256COLOURS 0x20000
-#define CLIENT_IDENTIFIED 0x40000
-#define CLIENT_STATUSFORCE 0x80000
-#define CLIENT_DOUBLECLICK 0x100000
-#define CLIENT_TRIPLECLICK 0x200000
-	int		 flags;
-	struct key_table *keytable;
-
-	struct event	 identify_timer;
-	void		(*identify_callback)(struct client *, struct window_pane *);
-	void		*identify_callback_data;
-
-	char		*message_string;
-	struct event	 message_timer;
-	u_int		 message_next;
-	TAILQ_HEAD(, message_entry) message_log;
-
-	char		*prompt_string;
-	struct utf8_data *prompt_buffer;
-	size_t		 prompt_index;
-	int		 (*prompt_callbackfn)(void *, const char *);
-	void		 (*prompt_freefn)(void *);
-	void		*prompt_data;
-	u_int		 prompt_hindex;
-	enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode;
-
-#define PROMPT_SINGLE 0x1
-#define PROMPT_NUMERIC 0x2
-	int		 prompt_flags;
-
-	struct session	*session;
-	struct session	*last_session;
-
-	int		 wlmouse;
-
-	struct cmd_q	*cmdq;
-	int		 references;
-
-	TAILQ_ENTRY(client) entry;
-};
-TAILQ_HEAD(clients, client);
-
 /* Parsed arguments structures. */
 struct args_entry;
 RB_HEAD(args_tree, args_entry);
@@ -1324,42 +1230,46 @@ enum cmd_retval {
 	CMD_RETURN_STOP
 };
 
-/* Command queue entry. */
-struct cmd_q_item {
-	struct cmd_list		*cmdlist;
-
-	struct mouse_event	 mouse;
-
-	TAILQ_ENTRY(cmd_q_item)	 qentry;
+/* Command queue item type. */
+enum cmd_q_type {
+	CMD_Q_COMMAND,
+	CMD_Q_CALLBACK,
 };
-TAILQ_HEAD(cmd_q_items, cmd_q_item);
 
-/* Command queue. */
+/* Command queue item. */
+typedef enum cmd_retval (*cmd_q_cb) (struct cmd_q *, void *);
 struct cmd_q {
-	int			 references;
-	int			 flags;
-#define CMD_Q_DEAD 0x1
-#define CMD_Q_NOHOOKS 0x2
+	struct cmd_q_list	*queue;
+	struct cmd_q		*next;
 
 	struct client		*client;
-	int			 client_exit;
 
-	struct cmd_q_items	 queue;
-	struct cmd_q_item	*item;
+	enum cmd_q_type		 type;
+	u_int			 group;
+
+	u_int			 number;
+	time_t			 time;
+
+	const char		*hook;
+	int			 flags;
+#define CMD_Q_FIRED 0x1
+#define CMD_Q_WAITING 0x2
+#define CMD_Q_NOHOOKS 0x4
+
+	struct cmd_list		*cmdlist;
 	struct cmd		*cmd;
-	struct cmd_q		*parent;
+
+	cmd_q_cb		 cb;
+	void			*data;
 
 	struct cmd_find_state	 current;
 	struct cmd_state	 state;
 
-	time_t			 time;
-	u_int			 number;
+	struct mouse_event	 mouse;
 
-	void			 (*emptyfn)(struct cmd_q *);
-	void			*data;
-
-	TAILQ_ENTRY(cmd_q)	 waitentry;
+	TAILQ_ENTRY(cmd_q)	 entry;
 };
+TAILQ_HEAD(cmd_q_list, cmd_q);
 
 /* Command -c, -t or -s flags. */
 enum cmd_entry_flag {
@@ -1409,6 +1319,101 @@ struct cmd_entry {
 	enum cmd_retval		 (*exec)(struct cmd *, struct cmd_q *);
 };
 
+/* Client connection. */
+struct client {
+	struct tmuxpeer	*peer;
+	struct cmd_q_list queue;
+
+	pid_t		 pid;
+	int		 fd;
+	struct event	 event;
+	int		 retval;
+
+	struct timeval	 creation_time;
+	struct timeval	 activity_time;
+
+	struct environ	*environ;
+
+	char		*title;
+	const char	*cwd;
+
+	char		*term;
+	char		*ttyname;
+	struct tty	 tty;
+
+	void		(*stdin_callback)(struct client *, int, void *);
+	void		*stdin_callback_data;
+	struct evbuffer	*stdin_data;
+	int		 stdin_closed;
+	struct evbuffer	*stdout_data;
+	struct evbuffer	*stderr_data;
+
+	struct event	 repeat_timer;
+
+	struct event	 click_timer;
+	u_int		 click_button;
+
+	struct event	 status_timer;
+	struct screen	 status;
+
+#define CLIENT_TERMINAL 0x1
+#define CLIENT_LOGIN 0x2
+#define CLIENT_EXIT 0x4
+#define CLIENT_REDRAW 0x8
+#define CLIENT_STATUS 0x10
+#define CLIENT_REPEAT 0x20
+#define CLIENT_SUSPENDED 0x40
+#define CLIENT_ATTACHED 0x80
+#define CLIENT_IDENTIFY 0x100
+#define CLIENT_DEAD 0x200
+#define CLIENT_BORDERS 0x400
+#define CLIENT_READONLY 0x800
+#define CLIENT_REDRAWWINDOW 0x1000
+#define CLIENT_CONTROL 0x2000
+#define CLIENT_CONTROLCONTROL 0x4000
+#define CLIENT_FOCUSED 0x8000
+#define CLIENT_UTF8 0x10000
+#define CLIENT_256COLOURS 0x20000
+#define CLIENT_IDENTIFIED 0x40000
+#define CLIENT_STATUSFORCE 0x80000
+#define CLIENT_DOUBLECLICK 0x100000
+#define CLIENT_TRIPLECLICK 0x200000
+	int		 flags;
+	struct key_table *keytable;
+
+	struct event	 identify_timer;
+	void		(*identify_callback)(struct client *, struct window_pane *);
+	void		*identify_callback_data;
+
+	char		*message_string;
+	struct event	 message_timer;
+	u_int		 message_next;
+	TAILQ_HEAD(, message_entry) message_log;
+
+	char		*prompt_string;
+	struct utf8_data *prompt_buffer;
+	size_t		 prompt_index;
+	int		 (*prompt_callbackfn)(void *, const char *);
+	void		 (*prompt_freefn)(void *);
+	void		*prompt_data;
+	u_int		 prompt_hindex;
+	enum { PROMPT_ENTRY, PROMPT_COMMAND } prompt_mode;
+
+#define PROMPT_SINGLE 0x1
+#define PROMPT_NUMERIC 0x2
+	int		 prompt_flags;
+
+	struct session	*session;
+	struct session	*last_session;
+
+	int		 wlmouse;
+
+	int		 references;
+
+	TAILQ_ENTRY(client) entry;
+};
+TAILQ_HEAD(clients, client);
+
 /* Key binding and key table. */
 struct key_binding {
 	key_code		 key;
@@ -1504,10 +1509,9 @@ void	proc_kill_peer(struct tmuxpeer *);
 
 /* cfg.c */
 extern int cfg_finished;
-extern int cfg_references;
 extern struct client *cfg_client;
 void		 start_cfg(void);
-int		 load_cfg(const char *, struct cmd_q *, int);
+int		 load_cfg(const char *, struct client *, struct cmd_q *, int);
 void		 set_cfg_file(const char *);
 void printflike(1, 2) cfg_add_cause(const char *, ...);
 void		 cfg_print_causes(struct cmd_q *);
@@ -1557,9 +1561,9 @@ void		 hooks_add(struct hooks *, const char *, struct cmd_list *);
 void		 hooks_copy(struct hooks *, struct hooks *);
 void		 hooks_remove(struct hooks *, const char *);
 struct hook	*hooks_find(struct hooks *, const char *);
-int printflike(4, 5) hooks_run(struct hooks *, struct client *,
+void printflike(4, 5) hooks_run(struct hooks *, struct client *,
 		    struct cmd_find_state *, const char *, ...);
-int printflike(4, 5) hooks_wait(struct hooks *, struct cmd_q *,
+void printflike(4, 5) hooks_insert(struct hooks *, struct cmd_q *,
 		    struct cmd_find_state *, const char *, ...);
 
 /* mode-key.c */
@@ -1755,8 +1759,7 @@ char	       **cmd_copy_argv(int, char **);
 void		 cmd_free_argv(int, char **);
 char		*cmd_stringify_argv(int, char **);
 struct cmd	*cmd_parse(int, char **, const char *, u_int, char **);
-int		 cmd_prepare_state(struct cmd *, struct cmd_q *,
-		     struct cmd_q *);
+int		 cmd_prepare_state(struct cmd *, struct cmd_q *);
 char		*cmd_print(struct cmd *);
 int		 cmd_mouse_at(struct window_pane *, struct mouse_event *,
 		     u_int *, u_int *, int);
@@ -1776,16 +1779,15 @@ void		 cmd_list_free(struct cmd_list *);
 char		*cmd_list_print(struct cmd_list *);
 
 /* cmd-queue.c */
-struct cmd_q	*cmdq_new(struct client *);
-int		 cmdq_free(struct cmd_q *);
+struct cmd_q	*cmdq_get_command(struct cmd_list *, struct cmd_find_state *,
+		     struct mouse_event *, int);
+struct cmd_q	*cmdq_get_callback(cmd_q_cb, void *);
+void		 cmdq_insert_after(struct cmd_q *, struct cmd_q *);
+void		 cmdq_append(struct client *, struct cmd_q *);
+u_int		 cmdq_next(struct client *);
+void		 cmdq_guard(struct cmd_q *, const char *, int);
 void printflike(2, 3) cmdq_print(struct cmd_q *, const char *, ...);
 void printflike(2, 3) cmdq_error(struct cmd_q *, const char *, ...);
-void		 cmdq_guard(struct cmd_q *, const char *, int);
-void		 cmdq_run(struct cmd_q *, struct cmd_list *,
-		     struct mouse_event *);
-void		 cmdq_append(struct cmd_q *, struct cmd_list *,
-		     struct mouse_event *);
-int		 cmdq_continue(struct cmd_q *);
 
 /* cmd-string.c */
 int	cmd_string_parse(const char *, struct cmd_list **, const char *,
diff --git a/window-choose.c b/window-choose.c
index 25dc77d1..b7720e8d 100644
--- a/window-choose.c
+++ b/window-choose.c
@@ -246,6 +246,7 @@ window_choose_data_run(struct window_choose_data *cdata)
 {
 	struct cmd_list	*cmdlist;
 	char		*cause;
+	struct cmd_q	*cmdq;
 
 	/*
 	 * The command template will have already been replaced. But if it's
@@ -263,7 +264,8 @@ window_choose_data_run(struct window_choose_data *cdata)
 		return;
 	}
 
-	cmdq_run(cdata->start_client->cmdq, cmdlist, NULL);
+	cmdq = cmdq_get_command(cmdlist, NULL, NULL, 0);
+	cmdq_append(cdata->start_client, cmdq);
 	cmd_list_free(cmdlist);
 }