summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-14 12:32:54 -0600
committerCraig Jennings <c@cjennings.net>2025-11-14 12:32:54 -0600
commit64d2eb519923ac5a4d0dbd8ace95cb5ac2639dfe (patch)
tree3ed9c7471538e8cb805a2e0584bfd015bf2eaaee
parent6aac8e849221321f6ef8dffcf1cde0c26602dabe (diff)
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
-rw-r--r--config.def.h1
-rw-r--r--patches_applied/st-newterm-0.9-tmux.diff140
-rw-r--r--patches_applied/st-newterm-0.9.diff112
-rw-r--r--st.c119
-rw-r--r--st.h1
5 files changed, 373 insertions, 0 deletions
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 <meator.dev@gmail.com>
+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 <meator.dev@gmail.com>
+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 <pty.h>
+ #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 <pty.h>
#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);
@@ -1127,6 +1133,119 @@ treset(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));
+ 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)
{
int i, j;
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 *);