The Aim
While doing some programming on the HATE terminal emulator, I came across an interesting idea: I wanted to be able to type a command into bash (from within HATE), that would leave the current bash session running, but open a new terminal, potentially running a particular process. For instance, I might type “hatecmd new vim
” and it should open a new vim terminal within HATE. I could then use HATE to switch back and forth between bash and vim.
The Problem
When bash is run within HATE, the bash process is a child process of the HATE process. When I type a command in bash, it spawns a new child of the bash process. For this command to get HATE to do something (open a new terminal), it needs to communicate with an ancestor process that knows nothing about it. There are standard ways for processes to talk to one another in the unix world, but they usually rely on either (a) the processes knowing about each other (such as a server which has many child processes to handle network requests—the server process knows about its children), or (b) there being one system-wide process being talked to (such as a system-wide service listening to a named pipe). Neither of these conditions apply to HATE, as one or more users may run HATE one or more times, and HATE doesn’t know about the processes that bash spawns.
Of Locks and Signals
I considered a number of alternatives for tackling this problem. One involved sending and catching user signals, obtaining locks and writing application data to disk for other processes to read. The more I thought about this approach the more and more problems I came across. So I abandoned this approach in favour of another.
There’s Already a Way
Thinking about the problem, I realised that there’s already a way for processes that are run within a pseudo terminal to send messages to the terminal—via escape sequences. For instance, certain commands will ask to change the title of the terminal they’re running in. This is often used so that when you ssh into other machines, the terminal title reflects the machine you’re on. To do this, the shell on the remote machine writes a special sequence of characters which are read and translated by the pseudo terminal which, rather than printing the characters to the display, uses them to change the window title.
So, this gives two alternative solutions to our problem:
- Write a new pseudo terminal for HATE with its own special escape sequences that can be used to open new tabs. This option is very involved and, since I know very little about writing pseudo terminals, would take me quite a while to get my head around.
- Piggy-back an existing escape sequence, such as “change title”.
I adopted the second approach, with a few measures to ensure that this was not done accidentally. Specifically:
- Each instance of HATE generates a random set of characters which are that instance’s HATE ID.
- The HATE ID is inserted into the environment of child processes as $HATE_ID.
- In order to run a special HATE command, the program
hatecmd
will:- Check that $HATE_ID exists and print an error if not.
- Set the terminal title to “HATECMD:$HATE_ID:<command>”.
- Wait for half a second in case HATE wants to print some output to the terminal.
Celebrate!
I implemented this and it works fantastically. I even wrote the following script so that vim will open in a new terminal when run from within HATE:
#!/usr/bin/python
import os
from subprocess import Popen
import sys
if 'HATE_ID' in os.environ:
cmd = ['hatecmd', 'new']
else:
cmd = []
cmd.append('/usr/bin/vim')
cmd.extend(sys.argv[1:])
p = Popen(cmd)
p.communicate()
sys.exit(p.returncode)