diff --git a/patch/newterm.c b/patch/newterm.c index d4a1735..f06a0ce 100644 --- a/patch/newterm.c +++ b/patch/newterm.c @@ -1,7 +1,15 @@ +extern char* argv0; + +static char* +getcwd_by_pid(pid_t pid) { + static char cwd[32]; + snprintf(cwd, sizeof cwd, "/proc/%d/cwd", pid); + return cwd; +} + void newterm(const Arg* a) { - int res; switch (fork()) { case -1: die("fork failed: %s\n", strerror(errno)); @@ -12,9 +20,23 @@ newterm(const Arg* a) die("fork failed: %s\n", strerror(errno)); break; case 0: - res = chdir(getcwd_by_pid(pid)); - execlp("st", "./st", NULL); - break; + #if OSC7_PATCH + if (term.cwd) { + if (chdir(term.cwd) == 0) { + /* We need to put the working directory also in PWD, so that + * the shell starts in the right directory if `cwd` is a + * symlink. */ + setenv("PWD", term.cwd, 1); + } + } else { + chdir(getcwd_by_pid(pid)); + } + #else + chdir(getcwd_by_pid(pid)); + #endif // OSC7_PATCH + + execl("/proc/self/exe", argv0, NULL); + exit(1); default: exit(0); } @@ -22,9 +44,3 @@ newterm(const Arg* a) wait(NULL); } } - -static char *getcwd_by_pid(pid_t pid) { - char buf[32]; - snprintf(buf, sizeof buf, "/proc/%d/cwd", pid); - return realpath(buf, NULL); -} \ No newline at end of file diff --git a/patch/newterm.h b/patch/newterm.h index ea7292c..8073349 100644 --- a/patch/newterm.h +++ b/patch/newterm.h @@ -1,2 +1 @@ void newterm(const Arg *); -static char *getcwd_by_pid(pid_t pid); \ No newline at end of file diff --git a/patch/osc7.c b/patch/osc7.c new file mode 100644 index 0000000..39ac0a0 --- /dev/null +++ b/patch/osc7.c @@ -0,0 +1,74 @@ +static int +hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return -1; +} + +int +osc7parsecwd(const char *uri) +{ + const char *auth, *host, *hostend; + char *path, decoded[PATH_MAX], thishost[_POSIX_HOST_NAME_MAX]; + size_t i, decodedlen, hostlen, urilen; + int h1, h2; + if (!term.cwd) { + term.cwd = xmalloc(sizeof(decoded)); + term.cwd[0] = '\0'; + } + /* reset cwd if uri is empty */ + if ((urilen = strlen(uri)) == 0) { + term.cwd[0] = '\0'; + return 1; + } + /* decode uri */ + for (decodedlen = 0, i = 0; i < urilen; i++) { + if (uri[i] == '%' && i <= urilen-3 && + (h1 = hex2int(uri[i+1])) >= 0 && (h2 = hex2int(uri[i+2])) >= 0) { + decoded[decodedlen++] = (h1 << 4) | h2; + i += 2; + } else { + decoded[decodedlen++] = uri[i]; + } + if (decodedlen == sizeof(decoded)) { + fprintf(stderr, "erresc (OSC 7): uri is too long\n"); + return 0; + } + } + decoded[decodedlen] = '\0'; + /* check scheme */ + if (decodedlen < 5 || strncmp("file:", decoded, 5) != 0) { + fprintf(stderr, "erresc (OSC 7): scheme is not supported: '%s'\n", uri); + return 0; + } + /* find start of authority */ + if (decodedlen < 7 || decoded[5] != '/' || decoded[6] != '/') { + fprintf(stderr, "erresc (OSC 7): invalid uri: '%s'\n", uri); + return 0; + } + auth = decoded + 7; + /* find start of path and reset cwd if path is missing */ + if ((path = strchr(auth, '/')) == NULL) { + term.cwd[0] = '\0'; + return 1; + } + /* ignore path if host is not localhost */ + host = ((host = memchr(auth, '@', path - auth)) != NULL) ? host+1 : auth; + hostend = ((hostend = memchr(host, ':', path - host)) != NULL) ? hostend : path; + hostlen = hostend - host; + if (gethostname(thishost, sizeof(thishost)) < 0) + thishost[0] = '\0'; + if (hostlen > 0 && + !(hostlen == 9 && strncmp("localhost", host, hostlen) == 0) && + !(hostlen == strlen(thishost) && strncmp(host, thishost, hostlen) == 0)) { + return 0; + } + memcpy(term.cwd, path, decodedlen - (path - decoded) + 1); + return 1; +} diff --git a/patch/osc7.h b/patch/osc7.h new file mode 100644 index 0000000..965add8 --- /dev/null +++ b/patch/osc7.h @@ -0,0 +1 @@ +static int osc7parsecwd(const char *); diff --git a/patch/st_include.c b/patch/st_include.c index 20c2386..193cc61 100644 --- a/patch/st_include.c +++ b/patch/st_include.c @@ -27,3 +27,6 @@ #if SYNC_PATCH #include "sync.c" #endif +#if OSC7_PATCH +#include "osc7.c" +#endif diff --git a/patch/st_include.h b/patch/st_include.h index acc2975..e75985a 100644 --- a/patch/st_include.h +++ b/patch/st_include.h @@ -30,3 +30,6 @@ #if SYNC_PATCH #include "sync.h" #endif +#if OSC7_PATCH +#include "osc7.h" +#endif diff --git a/patches.def.h b/patches.def.h index 65d883c..ae5d37b 100644 --- a/patches.def.h +++ b/patches.def.h @@ -301,6 +301,14 @@ */ #define OPENURLONCLICK_PATCH 0 +/* This patch allows st to fetch the current working directory through the OSC 7 escape + * sequence emitted by shells. Must be used with newterm patch. + * + * https://codeberg.org/dnkl/foot/wiki#spawning-new-terminal-instances-in-the-current-working-directory + * https://github.com/veltza/st-sx/commit/817865c2c6ed905af8849580e58bdcf399216fbd + */ +#define OSC7_PATCH 0 + /* This patch allows jumping between prompts by utilizing the OSC 133 escape sequence * emitted by shells. Must be used with either reflow or scrollback patch. * diff --git a/st.c b/st.c index 7b25227..62a3419 100644 --- a/st.c +++ b/st.c @@ -2654,6 +2654,11 @@ strhandle(void) } } return; + #if OSC7_PATCH + case 7: + osc7parsecwd((const char *)strescseq.args[1]); + return; + #endif // OSC7_PATCH case 8: /* Clear Hyperlinks */ return; case 10: @@ -2856,6 +2861,19 @@ strparse(void) if (*p == '\0') return; + /* preserve semicolons in window titles, icon names and OSC 7 sequences */ + if (strescseq.type == ']' && ( + p[0] <= '2' + #if OSC7_PATCH + || p[0] == '7' + #endif // OSC7_PATCH + ) && p[1] == ';') { + strescseq.args[strescseq.narg++] = p; + strescseq.args[strescseq.narg++] = p + 2; + p[1] = '\0'; + return; + } + while (strescseq.narg < STR_ARG_SIZ) { strescseq.args[strescseq.narg++] = p; while ((c = *p) != ';' && c != '\0') diff --git a/st.h b/st.h index cd6982e..0682a0e 100644 --- a/st.h +++ b/st.h @@ -205,6 +205,9 @@ typedef struct { ImageList *images_alt; /* sixel images for alternate screen */ #endif // SIXEL_PATCH Rune lastc; /* last printed char outside of sequence, 0 if control */ + #if OSC7_PATCH + char* cwd; /* current working directory */ + #endif // OSC7_PATCH } Term; typedef union {