From 80e6621298d8c05584ca8dbe90eadad2d54d5a54 Mon Sep 17 00:00:00 2001
From: Rafael Kitover <rkitover@gmail.com>
Date: Thu, 25 Jul 2024 21:55:40 +0000
Subject: [PATCH] Pass terminal data on socket on Cygwin platforms

On Cygwin and MSYS2 use a controlling pty for the server client handler
instead of passing a file descriptor over the socket which is not
possible on Cygwin platforms.

Attach an event to STDIN on the client, passing data from it to the
server and attach an event to the pty out_fd on the server client
handler and pass data from it to the client.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
---
 client.c        | 14 ++++++++++++++
 compat.h        |  5 +++++
 server-client.c | 12 ++++++++++--
 tmux-protocol.h |  5 +++++
 tmux.h          |  5 +++++
 tty.c           | 39 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/client.c b/client.c
index 26e48df3..9f6b02b9 100644
--- a/client.c
+++ b/client.c
@@ -244,6 +244,9 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
 	char			*line = NULL, **caps = NULL, *cause;
 	u_int			 ncaps = 0;
 	struct args_value	*values;
+#ifdef TTY_OVER_SOCKET
+	struct tty*		 tty = xmalloc(sizeof *tty);
+#endif
 
 	/* Set up the initial command. */
 	if (shell_command != NULL) {
@@ -397,6 +400,10 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
 	} else if (msg == MSG_SHELL)
 		proc_send(client_peer, msg, -1, NULL, 0);
 
+#ifdef TTY_OVER_SOCKET
+	tty_attach_stdin_to_socket(tty, client_peer);
+#endif
+
 	/* Start main loop. */
 	proc_loop(client_proc, NULL);
 
@@ -474,12 +481,14 @@ client_send_identify(const char *ttynam, const char *termname, char **caps,
 		    caps[i], strlen(caps[i]) + 1);
 	}
 
+#ifndef TTY_OVER_SOCKET
 	if ((fd = dup(STDIN_FILENO)) == -1)
 		fatal("dup failed");
 	proc_send(client_peer, MSG_IDENTIFY_STDIN, fd, NULL, 0);
 	if ((fd = dup(STDOUT_FILENO)) == -1)
 		fatal("dup failed");
 	proc_send(client_peer, MSG_IDENTIFY_STDOUT, fd, NULL, 0);
+#endif
 
 	pid = getpid();
 	proc_send(client_peer, MSG_IDENTIFY_CLIENTPID, -1, &pid, sizeof pid);
@@ -805,5 +814,10 @@ client_dispatch_attached(struct imsg *imsg)
 		system(data);
 		proc_send(client_peer, MSG_UNLOCK, -1, NULL, 0);
 		break;
+#ifdef TTY_OVER_SOCKET
+	case MSG_TTY_READ:
+		write(STDOUT_FILENO, data, datalen);
+		break;
+#endif
 	}
 }
diff --git a/compat.h b/compat.h
index f33dcf1a..00fe1b12 100644
--- a/compat.h
+++ b/compat.h
@@ -69,6 +69,11 @@
 #define __weak __attribute__ ((__weak__))
 #endif
 
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MSYS__)
+#define WIN32_PLATFORM
+#define TTY_OVER_SOCKET
+#endif
+
 #ifndef ECHOPRT
 #define ECHOPRT 0
 #endif
diff --git a/server-client.c b/server-client.c
index 24ea04fe..87e2a09a 100644
--- a/server-client.c
+++ b/server-client.c
@@ -22,6 +22,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -2898,6 +2899,11 @@ server_client_dispatch(struct imsg *imsg, void *arg)
 	case MSG_READ_DONE:
 		file_read_done(&c->files, imsg);
 		break;
+#ifdef TTY_OVER_SOCKET
+	case MSG_TTY_WRITE:
+		write(c->fd, imsg->data, datalen);
+		break;
+#endif
 	}
 }
 
@@ -3069,6 +3075,7 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
 			c->cwd = xstrdup("/");
 		log_debug("client %p IDENTIFY_CWD %s", c, data);
 		break;
+#ifndef TTY_OVER_SOCKET
 	case MSG_IDENTIFY_STDIN:
 		if (datalen != 0)
 			fatalx("bad MSG_IDENTIFY_STDIN size");
@@ -3081,6 +3088,7 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
 		c->out_fd = imsg_get_fd(imsg);
 		log_debug("client %p IDENTIFY_STDOUT %d", c, c->out_fd);
 		break;
+#endif
 	case MSG_IDENTIFY_ENVIRON:
 		if (datalen == 0 || data[datalen - 1] != '\0')
 			fatalx("bad MSG_IDENTIFY_ENVIRON string");
@@ -3109,8 +3117,8 @@ server_client_dispatch_identify(struct client *c, struct imsg *imsg)
 	c->name = name;
 	log_debug("client %p name is %s", c, c->name);
 
-#ifdef __CYGWIN__
-	c->fd = open(c->ttyname, O_RDWR|O_NOCTTY);
+#ifdef TTY_OVER_SOCKET
+	c->fd = open("/dev/ptmx", O_RDWR|O_NOCTTY);
 	c->out_fd = dup(c->fd);
 #endif
 
diff --git a/tmux-protocol.h b/tmux-protocol.h
index 3cf00c09..443b817d 100644
--- a/tmux-protocol.h
+++ b/tmux-protocol.h
@@ -68,6 +68,11 @@ enum msgtype {
 	MSG_WRITE_READY,
 	MSG_WRITE_CLOSE,
 	MSG_READ_CANCEL
+#ifdef TTY_OVER_SOCKET
+	,
+	MSG_TTY_READ,
+	MSG_TTY_WRITE
+#endif
 };
 
 /*
diff --git a/tmux.h b/tmux.h
index dfc81cee..3df49023 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2461,6 +2461,11 @@ void	tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
 void	tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
 void	tty_default_colours(struct grid_cell *, struct window_pane *);
 
+#ifdef TTY_OVER_SOCKET
+int		tty_attach_stdin_to_socket(struct tty *, struct tmuxpeer *);
+int		tty_attach_out_to_socket(struct tty *, struct client *);
+#endif
+
 /* tty-term.c */
 extern struct tty_terms tty_terms;
 u_int		 tty_term_ncodes(void);
diff --git a/tty.c b/tty.c
index 2c9b7299..b6e582ad 100644
--- a/tty.c
+++ b/tty.c
@@ -250,6 +250,11 @@ tty_write_callback(__unused int fd, __unused short events, void *data)
 	struct client	*c = tty->client;
 	size_t		 size = EVBUFFER_LENGTH(tty->out);
 	int		 nwrite;
+#ifdef TTY_OVER_SOCKET
+	enum msgtype	 msg = MSG_TTY_READ;
+
+	proc_send(c->peer, msg, -1, EVBUFFER_DATA(tty->out), size);
+#endif
 
 	nwrite = evbuffer_write(tty->out, c->fd);
 	if (nwrite == -1)
@@ -304,6 +309,40 @@ tty_open(struct tty *tty, char **cause)
 	return (0);
 }
 
+#ifdef TTY_OVER_SOCKET
+static struct tmuxpeer *client_peer;
+
+static void
+tty_stdin_socket_callback(int fd, __unused short events, void *data)
+{
+	struct tty	*tty = data;
+	int		 nread;
+	enum msgtype	 msg = MSG_TTY_WRITE;
+
+	nread = evbuffer_read(tty->in, fd, -1);
+	if (nread == 0 || nread == -1) {
+		event_del(&tty->event_in);
+		return;
+	}
+
+	proc_send(client_peer, msg, -1, EVBUFFER_DATA(tty->in), nread);
+}
+
+int
+tty_attach_stdin_to_socket(struct tty *tty, struct tmuxpeer *peer)
+{
+	client_peer = peer;
+
+	event_set(&tty->event_in, STDIN_FILENO, EV_PERSIST|EV_READ,
+	    tty_stdin_socket_callback, tty);
+	tty->in = evbuffer_new();
+	if (tty->in == NULL)
+		fatal("out of memory");
+
+	return (0);
+}
+#endif
+
 static void
 tty_start_timer_callback(__unused int fd, __unused short events, void *data)
 {