xterm defines a very convenient action called spawn-new-terminal(). Using that action you can duplicate your current terminal and obtain a shell in your current working directory even if you’re inside a program like vim or mutt.
From the man page:
spawn-new-terminal(params)
Spawn a new xterm process. This is available on
systems which have a modern version of the process
filesystem, e.g., "/proc", which xterm can read.
Use the "cwd" process entry, e.g., /proc/12345/cwd
to obtain the working directory of the process
which is running in the current xterm.
You can bind this action to a key using Xresources. For example I bind the action to Alt-n. Here’s the relevant part from my .Xdefaults:
xterm*VT100.translations: #override \n\ Meta <Key>n:spawn-new-terminal()
The problem is that on *BSD you don’t have a procfs, at least not one that looks like the one on Linux.
So to actually get this working, xterm needs another way to find out the current working directory (CWD) of the shell that runs inside of it. I found out that this is not so easy and after some tinkering I decided to go for a dirty hack. The idea is, if I can’t find out the cwd of a zsh process on my own then perhaps I can ask zsh gently to tell it.
Zsh, like any other shell, lets you define functions, however certain functions have a special meaning for zsh: they define hooks. We use 2 hooks here: chpwd(), called whenever the CWD of zsh changes, and zshexit(), called when the shell quits.
Here are the relevant parts from my .zshrc:
function chpwd()
{
echo -n ${PWD} > ${HOME}/.zsh/${PPID}.pwd
}
#call chpwd once on startup
chpwd
function zshexit()
{
rm ${HOME}/.zsh/${PPID}.pwd
}
Now for the xterm part. The relevant function is HandleSpawnTerminal() in misc.c. Here’s my modified version:
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
#include <limits.h>
#include <err.h>
/* ARGSUSED */
void
(Widget w GCC_UNUSED,
HandleSpawnTerminal* event GCC_UNUSED,
XEvent * params,
String *nparams)
Cardinal {
static char zshdir[] = ".zsh";
static char termpath[] = "xterm";
, forkpid;
pid_t pid;
uid_t uidstruct passwd *passwd;
char pathname[_POSIX_PATH_MAX];
char zshcwd[_POSIX_PATH_MAX];
char *home;
FILE *fp;
= getpid();
pid
= geteuid();
uid = getpwuid(uid);
passwd = passwd->pw_dir;
home
(pathname, sizeof pathname, "%s/%s/%ld.pwd", home, zshdir, (long)pid);
snprintf
= fopen(pathname, "r");
fp if(fp == NULL)
{
("couldn't open %s", pathname);
warnreturn;
}
if(fgets(zshcwd, sizeof zshcwd, fp) == NULL)
{
("couldn't read from %s", pathname);
warn(fp);
fclosereturn;
}
(fp);
fclose
/* The reaper will take care of cleaning up the child */
= fork();
forkpid if(forkpid < 0)
{
("Could not fork");
warnreturn;
}
if(forkpid == 0)
{
/* We are the child */
if(chdir(zshcwd) < 0)
(1, "could not chdir to %s", zshcwd);
err
unsigned myargc = *nparams + 1;
char **myargv = TypeMallocN(char *, myargc + 1);
unsigned n = 0;
[n++] = termpath;
myargv
while (n < myargc) {
[n++] = *params++;
myargv}
[n] = 0;
myargv(termpath, myargv);
execvp
/* If we get here, we've failed */
(1, "exec of '%s'", termpath);
err} else {
/* We are the parent
* we just live our life */
}
}
Now you just have to reconfigure xterm with –enable-exec-xterm and build it.