From 42ba6c1b229c92256274e848e9c5ff1d59d9081b Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Tue, 2 Aug 2022 11:09:26 +0000
Subject: [PATCH] Add a third state "all" to allow-passthrough to work even in
 invisible panes, from Sergei Grechanik in GitHub issue 3274.

---
 input.c         | 11 ++++++++---
 options-table.c | 10 ++++++++--
 screen-write.c  |  4 +++-
 tmux.1          |  8 +++++++-
 tmux.h          | 46 +++++++++++++++++++++++++++-------------------
 tty.c           | 21 ++++++++++++++-------
 6 files changed, 67 insertions(+), 33 deletions(-)

diff --git a/input.c b/input.c
index 4252428b..d8b334de 100644
--- a/input.c
+++ b/input.c
@@ -2242,22 +2242,27 @@ static int
 input_dcs_dispatch(struct input_ctx *ictx)
 {
 	struct window_pane	*wp = ictx->wp;
+	struct options		*oo = wp->options;
 	struct screen_write_ctx	*sctx = &ictx->ctx;
 	u_char			*buf = ictx->input_buf;
 	size_t			 len = ictx->input_len;
 	const char		 prefix[] = "tmux;";
 	const u_int		 prefixlen = (sizeof prefix) - 1;
+	long long		 allow_passthrough = 0;
 
 	if (wp == NULL)
 		return (0);
 	if (ictx->flags & INPUT_DISCARD)
 		return (0);
-	if (!options_get_number(ictx->wp->options, "allow-passthrough"))
+	allow_passthrough = options_get_number(oo, "allow-passthrough");
+	if (!allow_passthrough)
 		return (0);
 	log_debug("%s: \"%s\"", __func__, buf);
 
-	if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0)
-		screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen);
+	if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) {
+		screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen,
+		    allow_passthrough == 2);
+	}
 
 	return (0);
 }
diff --git a/options-table.c b/options-table.c
index d1e65f15..377835bb 100644
--- a/options-table.c
+++ b/options-table.c
@@ -88,6 +88,9 @@ static const char *options_table_detach_on_destroy_list[] = {
 static const char *options_table_extended_keys_list[] = {
 	"off", "on", "always", NULL
 };
+static const char *options_table_allow_passthrough_list[] = {
+	"off", "on", "all", NULL
+};
 
 /* Status line format. */
 #define OPTIONS_TABLE_STATUS_FORMAT1 \
@@ -804,11 +807,14 @@ const struct options_table_entry options_table[] = {
 	},
 
 	{ .name = "allow-passthrough",
-	  .type = OPTIONS_TABLE_FLAG,
+	  .type = OPTIONS_TABLE_CHOICE,
 	  .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
+	  .choices = options_table_allow_passthrough_list,
 	  .default_num = 0,
 	  .text = "Whether applications are allowed to use the escape sequence "
-	          "to bypass tmux."
+		  "to bypass tmux. Can be 'off' (disallowed), 'on' (allowed "
+		  "if the pane is visible), or 'all' (allowed even if the pane "
+		  "is invisible)."
 	},
 
 	{ .name = "allow-rename",
diff --git a/screen-write.c b/screen-write.c
index 213b533c..476fe4dd 100644
--- a/screen-write.c
+++ b/screen-write.c
@@ -2100,13 +2100,15 @@ screen_write_setselection(struct screen_write_ctx *ctx, const char *flags,
 
 /* Write unmodified string. */
 void
-screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len)
+screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
+    int allow_invisible_panes)
 {
 	struct tty_ctx	ttyctx;
 
 	screen_write_initctx(ctx, &ttyctx, 0);
 	ttyctx.ptr = str;
 	ttyctx.num = len;
+	ttyctx.allow_invisible_panes = allow_invisible_panes;
 
 	tty_write(tty_cmd_rawstring, &ttyctx);
 }
diff --git a/tmux.1 b/tmux.1
index a1298fe0..fa7fcbfb 100644
--- a/tmux.1
+++ b/tmux.1
@@ -4461,11 +4461,17 @@ Available pane options are:
 .Pp
 .Bl -tag -width Ds -compact
 .It Xo Ic allow-passthrough
-.Op Ic on | off
+.Op Ic on | off | all
 .Xc
 Allow programs in the pane to bypass
 .Nm
 using a terminal escape sequence (\eePtmux;...\ee\e\e).
+If set to
+.Ic on ,
+passthrough sequences will be allowed only if the pane is visible.
+If set to
+.Ic all ,
+they will be allowed even if the pane is invisible.
 .Pp
 .It Xo Ic allow-rename
 .Op Ic on | off
diff --git a/tmux.h b/tmux.h
index 3f720e50..4196a31c 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1431,38 +1431,45 @@ struct tty_ctx {
 	void			*ptr;
 	void			*ptr2;
 
+	/*
+	 * Whether this command should be sent even when the pane is not
+	 * visible (used for a passthrough sequence when allow-passthrough is
+	 * "all").
+	 */
+	int			 allow_invisible_panes;
+
 	/*
 	 * Cursor and region position before the screen was updated - this is
 	 * where the command should be applied; the values in the screen have
 	 * already been updated.
 	 */
-	u_int		 ocx;
-	u_int		 ocy;
+	u_int			 ocx;
+	u_int			 ocy;
 
-	u_int		 orupper;
-	u_int		 orlower;
+	u_int			 orupper;
+	u_int			 orlower;
 
 	/* Target region (usually pane) offset and size. */
-	u_int		 xoff;
-	u_int		 yoff;
-	u_int		 rxoff;
-	u_int		 ryoff;
-	u_int		 sx;
-	u_int		 sy;
+	u_int			 xoff;
+	u_int			 yoff;
+	u_int			 rxoff;
+	u_int			 ryoff;
+	u_int			 sx;
+	u_int			 sy;
 
 	/* The background colour used for clearing (erasing). */
-	u_int		 bg;
+	u_int			 bg;
 
 	/* The default colours and palette. */
-	struct grid_cell defaults;
-	struct colour_palette *palette;
+	struct grid_cell	 defaults;
+	struct colour_palette	*palette;
 
 	/* Containing region (usually window) offset and size. */
-	int		 bigger;
-	u_int		 wox;
-	u_int		 woy;
-	u_int		 wsx;
-	u_int		 wsy;
+	int			 bigger;
+	u_int			 wox;
+	u_int			 woy;
+	u_int			 wsx;
+	u_int			 wsy;
 };
 
 /* Saved message entry. */
@@ -2889,7 +2896,8 @@ void	 screen_write_collect_add(struct screen_write_ctx *,
 void	 screen_write_cell(struct screen_write_ctx *, const struct grid_cell *);
 void	 screen_write_setselection(struct screen_write_ctx *, const char *,
 	     u_char *, u_int);
-void	 screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int);
+void	 screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
+	     int);
 void	 screen_write_alternateon(struct screen_write_ctx *,
 	     struct grid_cell *, int);
 void	 screen_write_alternateoff(struct screen_write_ctx *,
diff --git a/tty.c b/tty.c
index 1f373821..a7ad536f 100644
--- a/tty.c
+++ b/tty.c
@@ -1626,13 +1626,20 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
 	if (ctx->set_client_cb == NULL)
 		return;
 	TAILQ_FOREACH(c, &clients, entry) {
-		if (!tty_client_ready(c))
-			continue;
-		state = ctx->set_client_cb(ctx, c);
-		if (state == -1)
-			break;
-		if (state == 0)
-			continue;
+		if (ctx->allow_invisible_panes) {
+		    if (c->session == NULL ||
+			c->tty.term == NULL ||
+			c->flags & CLIENT_SUSPENDED)
+			    continue;
+		} else {
+			if (!tty_client_ready(c))
+				continue;
+			state = ctx->set_client_cb(ctx, c);
+			if (state == -1)
+				break;
+			if (state == 0)
+				continue;
+		}
 		cmdfn(&c->tty, ctx);
 	}
 }