From 64d2eb519923ac5a4d0dbd8ace95cb5ac2639dfe Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 14 Nov 2025 12:32:54 -0600 Subject: apply newterm patch with tmux support Applied st-newterm-0.9.diff and st-newterm-0.9-tmux.diff with minor manual adjustments: - Added newterm() function declaration to st.h - Added Ctrl-Shift-Return keybinding to spawn new terminal - Integrated tmux-aware working directory detection Benefits: - Spawn new st instance with Ctrl-Shift-Return - New terminal inherits current working directory - Tmux-aware: detects CWD of current process in tmux session - Improves workflow efficiency for multi-terminal usage Note: tmux support is Linux-only and requires tmux to be direct child of st --- config.def.h | 1 + patches_applied/st-newterm-0.9-tmux.diff | 140 +++++++++++++++++++++++++++++++ patches_applied/st-newterm-0.9.diff | 112 +++++++++++++++++++++++++ st.c | 119 ++++++++++++++++++++++++++ st.h | 1 + 5 files changed, 373 insertions(+) create mode 100644 patches_applied/st-newterm-0.9-tmux.diff create mode 100644 patches_applied/st-newterm-0.9.diff diff --git a/config.def.h b/config.def.h index 319facd..6a86df4 100644 --- a/config.def.h +++ b/config.def.h @@ -224,6 +224,7 @@ static Shortcut shortcuts[] = { { TERMMOD, XK_Y, clippaste, {.i = 0} }, { ShiftMask, XK_Insert, clippaste, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, + { TERMMOD, XK_Return, newterm, {.i = 0} }, { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, }; diff --git a/patches_applied/st-newterm-0.9-tmux.diff b/patches_applied/st-newterm-0.9-tmux.diff new file mode 100644 index 0000000..cd73e60 --- /dev/null +++ b/patches_applied/st-newterm-0.9-tmux.diff @@ -0,0 +1,140 @@ +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 + diff --git a/patches_applied/st-newterm-0.9.diff b/patches_applied/st-newterm-0.9.diff new file mode 100644 index 0000000..5525e7a --- /dev/null +++ b/patches_applied/st-newterm-0.9.diff @@ -0,0 +1,112 @@ +From e1cf625f4f225a007a5835421896089669195e02 Mon Sep 17 00:00:00 2001 +From: meator +Date: Wed, 26 Oct 2022 13:05:38 +0200 +Subject: [PATCH] Add shortcut to spawn new terminal in the current dir + +This commit is inspired by Santtu's st-newterm-20220221-0.8.5.diff. It +removes the unused res variable, it makes use of _exit() instead of +exit() and it replaces execlp() with execl() (PATH searching is useless +when the path is absolute). +--- + config.def.h | 1 + + st.c | 38 ++++++++++++++++++++++++++++++++++++++ + st.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 91ab8ca..7c75246 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -201,6 +201,7 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { TERMMOD, XK_Return, newterm, {.i = 0} }, + }; + + /* +diff --git a/st.c b/st.c +index 62def59..0261283 100644 +--- a/st.c ++++ b/st.c +@@ -20,6 +20,8 @@ + #include "st.h" + #include "win.h" + ++extern char *argv0; ++ + #if defined(__linux) + #include + #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +@@ -153,6 +155,7 @@ typedef struct { + } STREscape; + + static void execsh(char *, char **); ++static int chdir_by_pid(pid_t pid); + static void stty(char **); + static void sigchld(int); + static void ttywriteraw(const char *, size_t); +@@ -806,6 +809,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args) + if (pledge("stdio rpath tty proc", NULL) == -1) + die("pledge\n"); + #endif ++ fcntl(m, F_SETFD, FD_CLOEXEC); + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); +@@ -1054,6 +1058,40 @@ tswapscreen(void) + tfulldirt(); + } + ++void ++newterm(const Arg* a) ++{ ++ switch (fork()) { ++ case -1: ++ die("fork failed: %s\n", strerror(errno)); ++ break; ++ case 0: ++ switch (fork()) { ++ case -1: ++ fprintf(stderr, "fork failed: %s\n", strerror(errno)); ++ _exit(1); ++ break; ++ case 0: ++ chdir_by_pid(pid); ++ execl("/proc/self/exe", argv0, NULL); ++ _exit(1); ++ break; ++ default: ++ _exit(0); ++ } ++ default: ++ wait(NULL); ++ } ++} ++ ++static int ++chdir_by_pid(pid_t pid) ++{ ++ char buf[32]; ++ snprintf(buf, sizeof buf, "/proc/%ld/cwd", (long)pid); ++ return chdir(buf); ++} ++ + void + tscrolldown(int orig, int n) + { +diff --git a/st.h b/st.h +index fd3b0d8..f2b03b0 100644 +--- a/st.h ++++ b/st.h +@@ -81,6 +81,7 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++void newterm(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); +-- +2.38.0 + diff --git a/st.c b/st.c index 57feebb..96a8e08 100644 --- a/st.c +++ b/st.c @@ -20,6 +20,8 @@ #include "st.h" #include "win.h" +extern char *argv0; + #if defined(__linux) #include #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -181,6 +183,7 @@ typedef struct { } STREscape; static void execsh(char *, char **); +static int chdir_by_pid(pid_t pid); static void stty(char **); static void sigchld(int); static void ttywriteraw(const char *, size_t); @@ -260,6 +263,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; @@ -881,6 +886,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args) if (pledge("stdio rpath tty proc", NULL) == -1) die("pledge\n"); #endif + fcntl(m, F_SETFD, FD_CLOEXEC); close(s); cmdfd = m; signal(SIGCHLD, sigchld); @@ -1126,6 +1132,119 @@ treset(void) tfulldirt(); } +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)); + break; + case 0: + switch (fork()) { + case -1: + fprintf(stderr, "fork failed: %s\n", strerror(errno)); + _exit(1); + break; + case 0: + 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; + default: + _exit(0); + } + default: + wait(NULL); + } +} + +static int +chdir_by_pid(pid_t pid) +{ + char buf[32]; + snprintf(buf, sizeof buf, "/proc/%ld/cwd", (long)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 tnew(int col, int row) { diff --git a/st.h b/st.h index e320538..9cf12fd 100644 --- a/st.h +++ b/st.h @@ -87,6 +87,7 @@ void draw(void); void kscrolldown(const Arg *); void kscrollup(const Arg *); +void newterm(const Arg *); void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); -- cgit v1.2.3