Discussion:
[dev] [st][PATCH] better plumbing on linux: find child shell cwd without shell gymnastics
John Soros
2018-11-01 07:46:06 UTC
Permalink
config.def.h | 6 +++++
x.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)

diff --git a/config.def.h b/config.def.h
index 87ebdbb..265a4ff 100644
--- a/config.def.h
+++ b/config.def.h
@@ -467,3 +467,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumber_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumber_cmd = "plumb";
diff --git a/x.c b/x.c
index 4345892..f319129 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,9 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -644,6 +647,77 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}

+char *
+subprocwd()
+{
+ struct dirent* dent;
+ DIR* srcdir = opendir("/proc/self/task");
+ char path[PATH_MAX];
+ FILE *fp;
+ int chpid;
+
+ if (srcdir == NULL)
+ {
+ return NULL;
+ }
+
+ while((dent = readdir(srcdir)) != NULL)
+ {
+ struct stat st;
+ char newdir[PATH_MAX];
+
+ if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ if (snprintf(newdir, PATH_MAX, "%s/%s", "/proc/self/task",
dent->d_name) < 0)
+ return NULL;
+ if (stat(newdir, &st) < 0)
+ continue;
+
+ if (S_ISDIR(st.st_mode)) break;
+ }
+ closedir(srcdir);
+ if (snprintf(path, PATH_MAX, "/proc/self/task/%s/children",
dent->d_name) < 0)
+ return NULL;
+ if (!(fp = fopen(path, "r")))
+ return NULL;
+ if (fscanf(fp, "%d", &chpid) != 1) {
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", chpid) < 0)
+ return NULL;
+ return realpath(path, NULL);
+}
+
+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char *cwd;
+ char *cmd;
+ pid_t child;
+ cwd = subprocwd();
+ if (cwd == NULL)
+ return;
+
+ switch(child = fork()) {
+ case -1:
+ return;
+ case 0:
+ if (cwd == NULL)
+ exit(1);
+ cmd = malloc(PATH_MAX+100);
+ if (snprintf(cmd, PATH_MAX+100, "(cd %s ; %s %s)", cwd, plumber_cmd,
sel) < 0)
+ exit(1);
+ free(cwd);
+ execvp("sh", (char *const []){"sh", "-c", cmd, 0});
+ exit(0);
+ default:
+ waitpid(child, NULL, 0);
+ }
+}
+
void
brelease(XEvent *e)
{
@@ -656,6 +730,8 @@ brelease(XEvent *e)
selpaste(NULL);
else if (e->xbutton.button == Button1)
mousesel(e, 1);
+ else if (e->xbutton.button == Button3)
+ plumb(xsel.primary);
}

void
Quentin Rameau
2018-11-01 08:33:37 UTC
Permalink
Hello John,
Post by John Soros
+ DIR* srcdir = opendir("/proc/self/task");
Is that a Linux‐only patch?
Markus Wichmann
2018-11-01 12:48:14 UTC
Permalink
Post by Quentin Rameau
Hello John,
Post by John Soros
+ DIR* srcdir = opendir("/proc/self/task");
Is that a Linux‐only patch?
The subject line would indicate thus. Funnily enough, *that* dependency
could be removed. What can't be removed is the dependency on
/proc/XXX/cwd. Is that available on whatever OS you care about (I know
it was one of the BSDs, just don't remember which)?

Ciao,
Markus
John Soros
2018-11-02 09:25:49 UTC
Permalink
Post by Quentin Rameau
Is that a Linux‐only patch?
Hi Quentin,
It isn't anymore.
Regs,
John

Markus Wichmann
2018-11-01 12:43:18 UTC
Permalink
Post by John Soros
config.def.h | 6 +++++
x.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/config.def.h b/config.def.h
index 87ebdbb..265a4ff 100644
--- a/config.def.h
+++ b/config.def.h
@@ -467,3 +467,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumber_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumber_cmd = "plumb";
diff --git a/x.c b/x.c
index 4345892..f319129 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,9 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -644,6 +647,77 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}
+char *
+subprocwd()
+{
+ struct dirent* dent;
+ DIR* srcdir = opendir("/proc/self/task");
+ char path[PATH_MAX];
+ FILE *fp;
+ int chpid;
+
+ if (srcdir == NULL)
+ {
+ return NULL;
+ }
+
+ while((dent = readdir(srcdir)) != NULL)
+ {
+ struct stat st;
+ char newdir[PATH_MAX];
+
+ if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ if (snprintf(newdir, PATH_MAX, "%s/%s", "/proc/self/task",
dent->d_name) < 0)
+ return NULL;
+ if (stat(newdir, &st) < 0)
+ continue;
+
+ if (S_ISDIR(st.st_mode)) break;
+ }
+ closedir(srcdir);
+ if (snprintf(path, PATH_MAX, "/proc/self/task/%s/children",
dent->d_name) < 0)
+ return NULL;
+ if (!(fp = fopen(path, "r")))
+ return NULL;
+ if (fscanf(fp, "%d", &chpid) != 1) {
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", chpid) < 0)
+ return NULL;
+ return realpath(path, NULL);
+}
+
You know, you can have that one cheaper: /proc/self/task will contain a
directory named for the TIDs of each thread of the process. As st is a
single-threaded process, that TID must always equal the PID. So the
first loop can be replaced with.

snprintf(path, PATH_MAX, "/proc/self/task/%d/children", getpid());

Also, your code assumes the shell to be the only child of st, but spawns
other children of st. How about you iterate over all processes, finding
the ones with a PPID equaling st's PID and having an exe pointing to the
shell (maybe remember the shell spawned in a global variable)? Speaking
of remembering things, might it not be easier to just remember the
shell's PID from the point it was spawned in the first place? Then you
don't need to iterate over rarely-available system interfaces.
Post by John Soros
+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char *cwd;
+ char *cmd;
+ pid_t child;
+ cwd = subprocwd();
+ if (cwd == NULL)
+ return;
+
So, if it all failed somehow, you won't even spawn the command in st's
current directory? Nor give any indication of failure to the user.
Post by John Soros
+ switch(child = fork()) {
+ return;
+ if (cwd == NULL)
+ exit(1);
+ cmd = malloc(PATH_MAX+100);
+ if (snprintf(cmd, PATH_MAX+100, "(cd %s ; %s %s)", cwd, plumber_cmd,
sel) < 0)
+ exit(1);
+ free(cwd);
+ execvp("sh", (char *const []){"sh", "-c", cmd, 0});
+ exit(0);
This won't work if there is a space in cwd. Which there might be. Also,
why change dir in the shell? Just change dir in the process. You have
the directory right there, just call chdir() and call it a day.

Also, do you want word separation to take place? Because if not, I see
no reason for the shell at all.

Ciao,
Markus
John Soros
2018-11-01 14:04:41 UTC
Permalink
Post by Markus Wichmann
Post by John Soros
config.def.h | 6 +++++
x.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/config.def.h b/config.def.h
index 87ebdbb..265a4ff 100644
--- a/config.def.h
+++ b/config.def.h
@@ -467,3 +467,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumber_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumber_cmd = "plumb";
diff --git a/x.c b/x.c
index 4345892..f319129 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,9 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -644,6 +647,77 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}
+char *
+subprocwd()
+{
+ struct dirent* dent;
+ DIR* srcdir = opendir("/proc/self/task");
+ char path[PATH_MAX];
+ FILE *fp;
+ int chpid;
+
+ if (srcdir == NULL)
+ {
+ return NULL;
+ }
+
+ while((dent = readdir(srcdir)) != NULL)
+ {
+ struct stat st;
+ char newdir[PATH_MAX];
+
+ if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ if (snprintf(newdir, PATH_MAX, "%s/%s", "/proc/self/task",
dent->d_name) < 0)
+ return NULL;
+ if (stat(newdir, &st) < 0)
+ continue;
+
+ if (S_ISDIR(st.st_mode)) break;
+ }
+ closedir(srcdir);
+ if (snprintf(path, PATH_MAX, "/proc/self/task/%s/children",
dent->d_name) < 0)
+ return NULL;
+ if (!(fp = fopen(path, "r")))
+ return NULL;
+ if (fscanf(fp, "%d", &chpid) != 1) {
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", chpid) < 0)
+ return NULL;
+ return realpath(path, NULL);
+}
+
You know, you can have that one cheaper: /proc/self/task will contain a
directory named for the TIDs of each thread of the process. As st is a
single-threaded process, that TID must always equal the PID. So the
first loop can be replaced with.
snprintf(path, PATH_MAX, "/proc/self/task/%d/children", getpid());
Also, your code assumes the shell to be the only child of st, but spawns
other children of st. How about you iterate over all processes, finding
the ones with a PPID equaling st's PID and having an exe pointing to the
shell (maybe remember the shell spawned in a global variable)? Speaking
of remembering things, might it not be easier to just remember the
shell's PID from the point it was spawned in the first place? Then you
don't need to iterate over rarely-available system interfaces.
Post by John Soros
+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char *cwd;
+ char *cmd;
+ pid_t child;
+ cwd = subprocwd();
+ if (cwd == NULL)
+ return;
+
So, if it all failed somehow, you won't even spawn the command in st's
current directory? Nor give any indication of failure to the user.
Post by John Soros
+ switch(child = fork()) {
+ return;
+ if (cwd == NULL)
+ exit(1);
+ cmd = malloc(PATH_MAX+100);
+ if (snprintf(cmd, PATH_MAX+100, "(cd %s ; %s %s)", cwd, plumber_cmd,
sel) < 0)
+ exit(1);
+ free(cwd);
+ execvp("sh", (char *const []){"sh", "-c", cmd, 0});
+ exit(0);
This won't work if there is a space in cwd. Which there might be. Also,
why change dir in the shell? Just change dir in the process. You have
the directory right there, just call chdir() and call it a day.
Also, do you want word separation to take place? Because if not, I see
no reason for the shell at all.
Ciao,
Markus
Thanks for the comments and advise. I will try to implement them soon.
As far as I can see, there is no solution for this on OpenBSD.
Regs,
John
John Soros
2018-11-01 15:12:40 UTC
Permalink
Post by John Soros
Post by Markus Wichmann
Post by John Soros
config.def.h | 6 +++++
x.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/config.def.h b/config.def.h
index 87ebdbb..265a4ff 100644
--- a/config.def.h
+++ b/config.def.h
@@ -467,3 +467,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumber_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumber_cmd = "plumb";
diff --git a/x.c b/x.c
index 4345892..f319129 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,9 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -644,6 +647,77 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}
+char *
+subprocwd()
+{
+ struct dirent* dent;
+ DIR* srcdir = opendir("/proc/self/task");
+ char path[PATH_MAX];
+ FILE *fp;
+ int chpid;
+
+ if (srcdir == NULL)
+ {
+ return NULL;
+ }
+
+ while((dent = readdir(srcdir)) != NULL)
+ {
+ struct stat st;
+ char newdir[PATH_MAX];
+
+ if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
+ continue;
+ if (snprintf(newdir, PATH_MAX, "%s/%s", "/proc/self/task",
dent->d_name) < 0)
+ return NULL;
+ if (stat(newdir, &st) < 0)
+ continue;
+
+ if (S_ISDIR(st.st_mode)) break;
+ }
+ closedir(srcdir);
+ if (snprintf(path, PATH_MAX, "/proc/self/task/%s/children",
dent->d_name) < 0)
+ return NULL;
+ if (!(fp = fopen(path, "r")))
+ return NULL;
+ if (fscanf(fp, "%d", &chpid) != 1) {
+ fclose(fp);
+ return NULL;
+ }
+ fclose(fp);
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", chpid) < 0)
+ return NULL;
+ return realpath(path, NULL);
+}
+
You know, you can have that one cheaper: /proc/self/task will contain a
directory named for the TIDs of each thread of the process. As st is a
single-threaded process, that TID must always equal the PID. So the
first loop can be replaced with.
snprintf(path, PATH_MAX, "/proc/self/task/%d/children", getpid());
Also, your code assumes the shell to be the only child of st, but spawns
other children of st. How about you iterate over all processes, finding
the ones with a PPID equaling st's PID and having an exe pointing to the
shell (maybe remember the shell spawned in a global variable)? Speaking
of remembering things, might it not be easier to just remember the
shell's PID from the point it was spawned in the first place? Then you
don't need to iterate over rarely-available system interfaces.
Post by John Soros
+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char *cwd;
+ char *cmd;
+ pid_t child;
+ cwd = subprocwd();
+ if (cwd == NULL)
+ return;
+
So, if it all failed somehow, you won't even spawn the command in st's
current directory? Nor give any indication of failure to the user.
Post by John Soros
+ switch(child = fork()) {
+ return;
+ if (cwd == NULL)
+ exit(1);
+ cmd = malloc(PATH_MAX+100);
+ if (snprintf(cmd, PATH_MAX+100, "(cd %s ; %s %s)", cwd, plumber_cmd,
sel) < 0)
+ exit(1);
+ free(cwd);
+ execvp("sh", (char *const []){"sh", "-c", cmd, 0});
+ exit(0);
This won't work if there is a space in cwd. Which there might be. Also,
why change dir in the shell? Just change dir in the process. You have
the directory right there, just call chdir() and call it a day.
Also, do you want word separation to take place? Because if not, I see
no reason for the shell at all.
Ciao,
Markus
Thanks for the comments and advise. I will try to implement them soon.
As far as I can see, there is no solution for this on OpenBSD.
Regs,
John
Yes! This is much, much better! Thanks. Pity that it still doesn't work
on OpenBSD.
config.def.h | 6 ++++++
st.c | 9 +++++++++
st.h | 2 ++
x.c | 29 +++++++++++++++++++++++++++++
4 files changed, 46 insertions(+)

diff --git a/config.def.h b/config.def.h
index 87ebdbb..b4f9b9a 100644
--- a/config.def.h
+++ b/config.def.h
@@ -467,3 +467,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumb_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumb_cmd = "plumb";
diff --git a/st.c b/st.c
index 3607ff1..ab7e78f 100644
--- a/st.c
+++ b/st.c
@@ -235,6 +235,15 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80,
0xE0, 0xF0, 0xF8};
static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800,
0x10000};
static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF,
0x10FFFF};

+char *
+subprocwd(void)
+{
+ char path[PATH_MAX];
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", pid) < 0)
+ return NULL;
+ return realpath(path, NULL);
+}
+
ssize_t
xwrite(int fd, const char *s, size_t len)
{
diff --git a/st.h b/st.h
index 0f2db33..eb53f49 100644
--- a/st.h
+++ b/st.h
@@ -125,6 +125,8 @@ char *xstrdup(char *);
void kscrolldown(const Arg *);
void kscrollup(const Arg *);

+char *subprocwd(void);
+
/* config.h globals */
extern char *utmp;
extern char *stty_args;
diff --git a/x.c b/x.c
index ce255ba..00e7db3 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,7 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -645,6 +646,32 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}

+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char *cwd;
+ pid_t child;
+ cwd = subprocwd();
+ if (cwd == NULL)
+ return;
+
+ switch(child = fork()) {
+ case -1:
+ return;
+ case 0:
+ if (chdir(cwd) != 0) {
+ free(cwd);
+ exit(1);
+ }
+ free(cwd);
+ execvp(plumb_cmd, (char *const []){plumb_cmd, sel, 0});
+ exit(0);
+ default:
+ waitpid(child, NULL, 0);
+ }
+}
+
void
brelease(XEvent *e)
{
@@ -657,6 +684,8 @@ brelease(XEvent *e)
selpaste(NULL);
else if (e->xbutton.button == Button1)
mousesel(e, 1);
+ else if (e->xbutton.button == Button3)
+ plumb(xsel.primary);
}

void
Markus Wichmann
2018-11-01 16:54:37 UTC
Permalink
Post by John Soros
Yes! This is much, much better! Thanks. Pity that it still doesn't work
on OpenBSD.
If the info I gathered over the course of the last hour is correct, then
the way to query CWD in OpenBSD is

#include <sys/sysctl.h>
char cwd[PATH_MAX];
size_t sz = sizeof cwd;
int name[3] = {CTL_KERN, KERN_PROC_CWD, pid};
sysctl(name, 3, cwd, &sz, 0, 0);

Also, if my understanding of Linux /proc is correct, then realpath()
might be overkill, and readlink() would already suffice. The links in
/proc aren't really symlinks. For instance, dietlibc's realpath() will
actually use readlink() on /proc/self/cwd to do its job... though, it
could just use readlink() on /proc/self/fd/XX, then it wouldn't need to
chdir()... I digress.

The question is how to incorporate such code. Do we create OS specific
source files and compile in the ones needed, or do we go for conditional
compilation? The former is more complicated, as it involves mapping out
an interface that each OS source file has to follow. And in the long
run, it might start sucking, as the OSes aren't as orthogonal as
originally thought, and we end up copying some functions, and then
having to copy bugfixes... sucks a bit. Conditionals also suck a bit, as
we end up seeing a lot of what amounts to commented-out code. Giving up
on unportable features also sucks, as it precludes useful features like
the one in this submission. So, which option sucks least?

Ciao,
Markus
Jérôme Andrieux
2018-11-01 19:40:15 UTC
Permalink
Post by Markus Wichmann
So, which option sucks least?
TLDR: OSC7 from the shell, IMO.

Given that I use st/zsh on OpenBSD more than Linux and wanted to add
some 9term/rc mouse features to its glory, I tried quite a few tricks
to get the current shell cwd.
I decided on using the OSC7 approach, ie push from the shell to st,
which introduced short and generic code in st and seems supported by
many shells.
I have not tried ksh though but worst case scenario, you need a "cd"
alias and some "printf".

I have been using it daily without troubles since I published it.
Oh, and it's great to be able to use the mouse to plumb any arbitrary string.

https://st.suckless.org/patches/right_click_to_plumb/
John Soros
2018-11-02 09:06:40 UTC
Permalink
Post by Markus Wichmann
Post by John Soros
Yes! This is much, much better! Thanks. Pity that it still doesn't work
on OpenBSD.
If the info I gathered over the course of the last hour is correct, then
the way to query CWD in OpenBSD is
#include <sys/sysctl.h>
char cwd[PATH_MAX];
size_t sz = sizeof cwd;
int name[3] = {CTL_KERN, KERN_PROC_CWD, pid};
sysctl(name, 3, cwd, &sz, 0, 0);
Nice!
Post by Markus Wichmann
Also, if my understanding of Linux /proc is correct, then realpath()
might be overkill, and readlink() would already suffice.
Looks to me like even readlink is overkill!
Post by Markus Wichmann
The question is how to incorporate such code. Do we create OS specific
source files and compile in the ones needed, or do we go for conditional
compilation? The former is more complicated, as it involves mapping out
an interface that each OS source file has to follow. And in the long
run, it might start sucking, as the OSes aren't as orthogonal as
originally thought, and we end up copying some functions, and then
having to copy bugfixes... sucks a bit. Conditionals also suck a bit, as
we end up seeing a lot of what amounts to commented-out code. Giving up
on unportable features also sucks, as it precludes useful features like
the one in this submission. So, which option sucks least?
I decided to use conditional compilation because the code already
contains some of that, and I would feel bad for creating a separate file
for so little code.
Post by Markus Wichmann
Ciao,
Markus
Thank you for the help, Markus, I am really happy with the result! I
guess freebsd, apple, netbsd and dragonfly are still a question, so I'm
not sure this patch should be included in the main distribution anyways.
I would be very happy if it was in the patches section, already.
I tested the patch on linux and openbsd 6.4.
Cheers,
John

config.def.h | 6 ++++++
st.c | 21 ++++++++++++++++++++-
st.h | 2 ++
x.c | 26 ++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/config.def.h b/config.def.h
index 823e79f..08e6ed4 100644
--- a/config.def.h
+++ b/config.def.h
@@ -459,3 +459,9 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * plumb_cmd is run on mouse button 3 click, with argument set to
+ * current selection and with cwd set to the cwd of the active shell
+ */
+static char *plumb_cmd = "plumb";
diff --git a/st.c b/st.c
index 46cf2da..9a2ad65 100644
--- a/st.c
+++ b/st.c
@@ -27,6 +27,9 @@
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#endif
+#if defined(__OpenBSD__)
+ #include <sys/sysctl.h>
+#endif

/* Arbitrary sizes */
#define UTF_INVALID 0xFFFD
@@ -232,6 +235,22 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80,
0xE0, 0xF0, 0xF8};
static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800,
0x10000};
static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF,
0x10FFFF};

+int
+subprocwd(char *path)
+{
+#if defined(__linux)
+ if (snprintf(path, PATH_MAX, "/proc/%d/cwd", pid) < 0)
+ return -1;
+ return 0;
+#elif defined(__OpenBSD__)
+ size_t sz = PATH_MAX;
+ int name[3] = {CTL_KERN, KERN_PROC_CWD, pid};
+ if (sysctl(name, 3, path, &sz, 0, 0) == -1)
+ return -1;
+ return 0;
+#endif
+}
+
ssize_t
xwrite(int fd, const char *s, size_t len)
{
@@ -810,7 +829,7 @@ ttynew(char *line, char *cmd, char *out, char **args)
break;
default:
#ifdef __OpenBSD__
- if (pledge("stdio rpath tty proc", NULL) == -1)
+ if (pledge("stdio rpath tty proc ps exec", NULL) == -1)
die("pledge\n");
#endif
close(s);
diff --git a/st.h b/st.h
index 38c61c4..1f87287 100644
--- a/st.h
+++ b/st.h
@@ -110,6 +110,8 @@ void *xmalloc(size_t);
void *xrealloc(void *, size_t);
char *xstrdup(char *);

+int subprocwd(char *);
+
/* config.h globals */
extern char *utmp;
extern char *stty_args;
diff --git a/x.c b/x.c
index 00cb6b1..e03dc71 100644
--- a/x.c
+++ b/x.c
@@ -5,6 +5,7 @@
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
+#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
@@ -635,6 +636,29 @@ xsetsel(char *str)
setsel(str, CurrentTime);
}

+void
+plumb(char *sel) {
+ if (sel == NULL)
+ return;
+ char cwd[PATH_MAX];
+ pid_t child;
+ if (subprocwd(cwd) != 0)
+ return;
+
+ switch(child = fork()) {
+ case -1:
+ return;
+ case 0:
+ if (chdir(cwd) != 0)
+ exit(1);
+ if (execvp(plumb_cmd, (char *const []){plumb_cmd, sel, 0}) == -1)
+ exit(1);
+ exit(0);
+ default:
+ waitpid(child, NULL, 0);
+ }
+}
+
void
brelease(XEvent *e)
{
@@ -647,6 +671,8 @@ brelease(XEvent *e)
selpaste(NULL);
else if (e->xbutton.button == Button1)
mousesel(e, 1);
+ else if (e->xbutton.button == Button3)
+ plumb(xsel.primary);
}

void
Loading...