From 2372b0fdc67f17336e39d8eb86019103aad6bb4e Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Thu, 18 Jun 2020 08:34:22 +0000
Subject: [PATCH] Add a flag to make a client wait for an empty line before
 exiting in control mode to avoid stray commands ending up in the shell.

---
 client.c        | 38 +++++++++++++++++++++++++++++++++-----
 control.c       |  1 +
 server-client.c |  5 +++++
 server.c        |  2 +-
 tmux.1          |  2 ++
 tmux.h          |  2 ++
 6 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/client.c b/client.c
index c97df491..852b09ce 100644
--- a/client.c
+++ b/client.c
@@ -35,7 +35,7 @@
 
 static struct tmuxproc	*client_proc;
 static struct tmuxpeer	*client_peer;
-static int		 client_flags;
+static uint64_t		 client_flags;
 static enum {
 	CLIENT_EXIT_NONE,
 	CLIENT_EXIT_DETACHED,
@@ -247,7 +247,9 @@ client_main(struct event_base *base, int argc, char **argv, int flags, int feat)
 	pid_t			 ppid;
 	enum msgtype		 msg;
 	struct termios		 tio, saved_tio;
-	size_t			 size;
+	size_t			 size, linesize = 0;
+	ssize_t			 linelen;
+	char			*line = NULL;
 
 	/* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */
 	signal(SIGCHLD, SIG_IGN);
@@ -276,13 +278,14 @@ client_main(struct event_base *base, int argc, char **argv, int flags, int feat)
 			free(pr->error);
 	}
 
-	/* Save the flags. */
-	client_flags = flags;
-
 	/* Create client process structure (starts logging). */
 	client_proc = proc_start("client");
 	proc_set_signals(client_proc, client_signal);
 
+	/* Save the flags. */
+	client_flags = flags;
+	log_debug("flags are %#llx", client_flags);
+
 	/* Initialize the client socket and start the server. */
 	fd = client_connect(base, socket_path, client_flags);
 	if (fd == -1) {
@@ -406,8 +409,19 @@ client_main(struct event_base *base, int argc, char **argv, int flags, int feat)
 			printf("%%exit %s\n", client_exit_message());
 		else
 			printf("%%exit\n");
+		fflush(stdout);
+		if (client_flags & CLIENT_CONTROL_WAITEXIT) {
+			setvbuf(stdin, NULL, _IOLBF, 0);
+			for (;;) {
+				linelen = getline(&line, &linesize, stdin);
+				if (linelen <= 1)
+					break;
+			}
+			free(line);
+		}
 		if (client_flags & CLIENT_CONTROLCONTROL) {
 			printf("\033\\");
+			fflush(stdout);
 			tcsetattr(STDOUT_FILENO, TCSAFLUSH, &saved_tio);
 		}
 	} else if (client_exitreason != CLIENT_EXIT_NONE)
@@ -870,6 +884,13 @@ client_dispatch_wait(struct imsg *imsg)
 		client_exitval = 1;
 		proc_exit(client_proc);
 		break;
+	case MSG_FLAGS:
+		if (datalen != sizeof client_flags)
+			fatalx("bad MSG_FLAGS string");
+
+		memcpy(&client_flags, data, sizeof client_flags);
+		log_debug("new flags are %#llx", client_flags);
+		break;
 	case MSG_SHELL:
 		if (datalen == 0 || data[datalen - 1] != '\0')
 			fatalx("bad MSG_SHELL string");
@@ -916,6 +937,13 @@ client_dispatch_attached(struct imsg *imsg)
 	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
 
 	switch (imsg->hdr.type) {
+	case MSG_FLAGS:
+		if (datalen != sizeof client_flags)
+			fatalx("bad MSG_FLAGS string");
+
+		memcpy(&client_flags, data, sizeof client_flags);
+		log_debug("new flags are %#llx", client_flags);
+		break;
 	case MSG_DETACH:
 	case MSG_DETACHKILL:
 		if (datalen == 0 || data[datalen - 1] != '\0')
diff --git a/control.c b/control.c
index 05093d94..5681d2dc 100644
--- a/control.c
+++ b/control.c
@@ -695,6 +695,7 @@ control_discard(struct client *c)
 
 	RB_FOREACH(cp, control_panes, &cs->panes)
 		control_discard_pane(c, cp);
+	bufferevent_disable(cs->read_event, EV_READ);
 }
 
 /* Stop control mode. */
diff --git a/server-client.c b/server-client.c
index 9e7adf55..d86a9fb8 100644
--- a/server-client.c
+++ b/server-client.c
@@ -2370,6 +2370,8 @@ server_client_control_flags(struct client *c, const char *next)
 	}
 	if (strcmp(next, "no-output") == 0)
 		return (CLIENT_CONTROL_NOOUTPUT);
+	if (strcmp(next, "wait-exit") == 0)
+		return (CLIENT_CONTROL_WAITEXIT);
 	return (0);
 }
 
@@ -2409,6 +2411,7 @@ server_client_set_flags(struct client *c, const char *flags)
 			control_reset_offsets(c);
 	}
 	free(copy);
+	proc_send(c->peer, MSG_FLAGS, -1, &c->flags, sizeof c->flags);
 }
 
 /* Get client flags. This is only flags useful to show to users. */
@@ -2427,6 +2430,8 @@ server_client_get_flags(struct client *c)
 		strlcat(s, "ignore-size,", sizeof s);
 	if (c->flags & CLIENT_CONTROL_NOOUTPUT)
 		strlcat(s, "no-output,", sizeof s);
+	if (c->flags & CLIENT_CONTROL_WAITEXIT)
+		strlcat(s, "wait-exit,", sizeof s);
 	if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
 		xsnprintf(tmp, sizeof tmp, "pause-after=%u,",
 		    c->pause_age / 1000);
diff --git a/server.c b/server.c
index 67d47f23..f07bf673 100644
--- a/server.c
+++ b/server.c
@@ -45,7 +45,7 @@ struct clients		 clients;
 
 struct tmuxproc		*server_proc;
 static int		 server_fd = -1;
-static int		 server_client_flags;
+static uint64_t		 server_client_flags;
 static int		 server_exit;
 static struct event	 server_ev_accept;
 
diff --git a/tmux.1 b/tmux.1
index 3c017e64..275dd7e5 100644
--- a/tmux.1
+++ b/tmux.1
@@ -989,6 +989,8 @@ output is paused once the pane is
 behind in control mode
 .It read-only
 the client is read-only
+.It wait-exit
+wait for an empty line input before exiting in control mode
 .El
 .Pp
 A leading
diff --git a/tmux.h b/tmux.h
index 837566ca..0a1a740b 100644
--- a/tmux.h
+++ b/tmux.h
@@ -515,6 +515,7 @@ enum msgtype {
 	MSG_UNLOCK,
 	MSG_WAKEUP,
 	MSG_EXEC,
+	MSG_FLAGS,
 
 	MSG_READ_OPEN = 300,
 	MSG_READ,
@@ -1644,6 +1645,7 @@ struct client {
 #define CLIENT_NOFORK 0x40000000
 #define CLIENT_ACTIVEPANE 0x80000000ULL
 #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL
+#define CLIENT_CONTROL_WAITEXIT 0x200000000ULL
 #define CLIENT_ALLREDRAWFLAGS		\
 	(CLIENT_REDRAWWINDOW|		\
 	 CLIENT_REDRAWSTATUS|		\