From 6640cf9809086d8cfb2363571d3e71a1a7a9f6bd Mon Sep 17 00:00:00 2001 From: meator Date: Tue, 25 Oct 2022 20:19:28 +0200 Subject: [PATCH] Add support for tmux in newterm This commit tries to figure out if st's child is tmux and if so, it launches a shell with the CWD of the current process in the tmux session instead of the tmux client itself. This is heavily inspired by https://gist.github.com/TiddoLangerak/c61e1e48df91192f9554 (but converted to C). Tmux has to be a direct child of st. This means that you'll have to usually use the 'exec' shell builtin to attach tmux sessions. This patch only works on Linux. Other systems use different procfs which is incompatible or don't have procfs at all (or it's optional). --- st.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/st.c b/st.c index 0261283..b95bf7a 100644 --- a/st.c +++ b/st.c @@ -221,6 +221,8 @@ static char base64dec_getc(const char **); static ssize_t xwrite(int, const char *, size_t); +static int gettmuxpts(void); + /* Globals */ static Term term; static Selection sel; @@ -1061,6 +1063,12 @@ tswapscreen(void) void newterm(const Arg* a) { + int pts; + FILE *fsession, *fpid; + char session[5]; + char pidstr[10]; + char buf[48]; + size_t size; switch (fork()) { case -1: die("fork failed: %s\n", strerror(errno)); @@ -1072,7 +1080,37 @@ newterm(const Arg* a) _exit(1); break; case 0: - chdir_by_pid(pid); + signal(SIGCHLD, SIG_DFL); /* pclose() needs to use wait() */ + pts = gettmuxpts(); + if (pts != -1) { + snprintf(buf, sizeof buf, "tmux lsc -t /dev/pts/%d -F \"#{client_session}\"", pts); + fsession = popen(buf, "r"); + if (!fsession) { + fprintf(stderr, "Couldn't launch tmux."); + _exit(1); + } + size = fread(session, 1, sizeof session, fsession); + if (pclose(fsession) != 0 || size == 0) { + fprintf(stderr, "Couldn't get tmux session."); + _exit(1); + } + session[size - 1] = '\0'; /* size - 1 is used to also trim the \n */ + + snprintf(buf, sizeof buf, "tmux list-panes -st %s -F \"#{pane_pid}\"", session); + fpid = popen(buf, "r"); + if (!fpid) { + fprintf(stderr, "Couldn't launch tmux."); + _exit(1); + } + size = fread(pidstr, 1, sizeof pidstr, fpid); + if (pclose(fpid) != 0 || size == 0) { + fprintf(stderr, "Couldn't get tmux session."); + _exit(1); + } + pidstr[size - 1] = '\0'; + } + + chdir_by_pid(pts != -1 ? atol(pidstr) : pid); execl("/proc/self/exe", argv0, NULL); _exit(1); break; @@ -1092,6 +1130,49 @@ chdir_by_pid(pid_t pid) return chdir(buf); } +/* returns the pty of tmux client or -1 if the child of st isn't tmux */ +static int +gettmuxpts(void) +{ + char buf[32]; + char comm[17]; + int tty; + FILE *fstat; + FILE *fcomm; + size_t numread; + + snprintf(buf, sizeof buf, "/proc/%ld/comm", (long)pid); + + fcomm = fopen(buf, "r"); + if (!fcomm) { + fprintf(stderr, "Couldn't open %s: %s\n", buf, strerror(errno)); + _exit(1); + } + + numread = fread(comm, 1, sizeof comm - 1, fcomm); + comm[numread] = '\0'; + + fclose(fcomm); + + if (strcmp("tmux: client\n", comm) != 0) + return -1; + + snprintf(buf, sizeof buf, "/proc/%ld/stat", (long)pid); + fstat = fopen(buf, "r"); + if (!fstat) { + fprintf(stderr, "Couldn't open %s: %s\n", buf, strerror(errno)); + _exit(1); + } + + /* + * We can't skip the second field with %*s because it contains a space so + * we skip strlen("tmux: client") + 2 for the braces which is 14. + */ + fscanf(fstat, "%*d %*14c %*c %*d %*d %*d %d", &tty); + fclose(fstat); + return ((0xfff00000 & tty) >> 12) | (0xff & tty); +} + void tscrolldown(int orig, int n) { -- 2.38.0