(Re)generate: (find-channels-intro) Source code: (find-eev "eev-intro.el" "find-channels-intro") More intros: (find-eev-quick-intro) (find-eev-intro) (find-eval-intro) (find-eepitch-intro) This buffer is _temporary_ and _editable_. It is meant as both a tutorial and a sandbox.1. Introduction
Before eepitch had been invented eev had two other ways to send commands to external shell-like programs. The first way, described here, (find-prepared-intro) (find-prepared-intro "2. `ee'") was technically very simple: running `M-x eev' would save a series of commands - usually the contents of the region - into a temporary script file, and then the user would type something like `ee' at the prompt of a ("prepared") shell; that would make it read the saved commands and execute them. Here we will describe the second of the Old Ways - one in which the target program, which is usually a shell running in an xterm, is sent a signal saying "execute the command NOW" as soon as a command is saved into a temporary file by Emacs. The difficulty is that this requires not only a directory for temporary files, but also an Expect script, which acts as an intermediary that listens to signals and handles them pretending that the saved commands came from the keyboard... and, as some people have said, this is "a nightmare to set up". Here we explain the protocol - that can be adapted to other cases too, like, for example, to make Emacs talk to SmallTalk environments and to computer algebra systems with GUIs - and we will present several tests that should help with troubleshooting.2. Low-level tests
Before we start the theory please try these low-level tests.2.1. Preparation
Have have to make sure that: 1) "eev-channel.el" has been loaded, 2) the "$EEVDIR/eegchannel" script exists and is executable, 3) we have expect installed - eegchannel depends on it, 4) the temporary directory "$EEVTMPDIR/" exists. Here is an e-script for that: (add-to-list 'load-path (ee-expand "$EEVDIR")) (require 'eev-channel) * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) sudo apt-get install expect echo $EEVDIR cd $EEVDIR pwd wget -nc http://anggtwu.net/eev-current/eegchannel chmod 755 eegchannel ls -lAF $EEVDIR/eegchannel expect -v $EEVDIR/eegchannel A echo ok echo $EEVTMPDIR mkdir -p $EEVTMPDIR/ # (find-eev "eegchannel")2.2. Test eegchannel
In this test we create a window setting like this, ______________________ | | | | | shell | | this |___________| | intro | | | | shell 2 | |__________|___________| and then: 1) we run "eegchannel A python" at the lower shell. This runs a python interpreter "listening on channel A"; 2) we use the top shell to send the lines "print(2+3)" and "exit()" "through the channel A". In low-level terms this means that for each line a) we save it into the file "$EEVTMPDIR/eeg.A.str", b) we send a signal SIGUSR1 to the process - "eegchannel" - whose PID is stored in the file "$EEVTMPDIR/eeg.A.pid". c) When the eegchannel listening on the channel A receives a SIGUSR1 it sends the line in "$EEVTMPDIR/eeg.A.str" to the process that it controls (that is "python") "as if the user had typed it". Here is the demo. Run it with <F8>s: * (find-3EE '(eepitch-shell) '(eepitch-shell2)) * (find-3ee '(eepitch-shell) '(eepitch-shell2)) * (eepitch-shell) # Optional clean-up: ls -lAF $EEVTMPDIR/eeg.A.* rm -fv $EEVTMPDIR/eeg.A.* * (eepitch-shell2) # Start a python interpreter "listening on channel A": $EEVDIR/eegchannel A python3 * (eepitch-shell) # Check that eeg.A.pid exists and is very recent: date ls -lAF $EEVTMPDIR/eeg.A.* # Send lines to eegchannel: echo 'print(2+3)' > $EEVTMPDIR/eeg.A.str kill -USR1 $(cat $EEVTMPDIR/eeg.A.pid) echo 'exit()' > $EEVTMPDIR/eeg.A.str kill -USR1 $(cat $EEVTMPDIR/eeg.A.pid)2.3. Test `eechannel'
TODO: write this. See: (find-eev "eev-channels.el" "eechannel")2.4. Test `eexterm'
TODO: write this. See: (find-eev "eev-channels.el" "eexterm")3. The innards
Let's start with a detailed low-level view of of what we have just summarized as to "save a command into a temporary file, then send a signal to the external program etc etc". Warning: this document is still VERY INCOMPLETE, and some parts of it are just notes, drafts, and links that may not work for you... =( (find-eev "eegchannel") (find-eev "anim/") (find-eev "anim/channels.anim") http://anggtwu.net/eev-current/eegchannel.html4. The protocol, in diagrams
Here's a diagram that shows roughly what we have when X is running both an Emacs and an xterm, each in a separate window. Many details have been omitted - for examples, the real communication happens through fifos and ptys, that are not shown - but it's easy to build a complete diagram from this. The arrows indicate flow of information. keyboard mouse display | | ^ v v | +----------------------------+ | | | X | | | +----------------------------+ key/mouse | ^ display key/mouse | ^ display events v | commands events v | commands +---------+ +---------+ | | | | | emacs | | xterm | | | | | +---------+ +---------+ chars and | ^ chars and signals v | signals +---------+ | | | sh | | | +---------+ To make the external shell at the right able to receive commands from Emacs we will insert a program between the "xterm" and the "sh" boxes - an Expect script called eechannel, that will usually be totally transparent, in the sense that it will let all the chars and signals that it receives pass through it without changes. The trick is that "eechannel" will also be "listening to commands from the outside", according to the following protocol: 1) each instance of eechannel "listens" to a fixed, named "channel"; for simplicity, let's suppose that this name is "A". 2) When an eechannel receives a signal SIGUSR1 it reads a string from the file "eech.A.str" and sends that to the shell, as if the user had typed that. 3) To make it simpler to send the signal, when eechannel starts up it saves its pid into the file "eech.A.pid". That protocol can be depicted as the four horizontal arrows below; the temporal order is from top to bottom. +-------------+ | | | xterm | | | +-------------+ +-----------+ | ^ | | --> eeg.A.str v | | emacs | <-- eeg.A.pid +-------------+ | | -----------------> | | +-----------+ eeg.A.str ---> | eechannel A | | | +-------------+ | ^ v | +-------------+ | | | sh | | | +-------------+ When Emacs wants to send a line, say, "cd /tmp/", to eechannel, it does this: 1) "--> eeg.A.str " writes "cd /tmp/" into eech.A.str, 2) "<-- eeg.A.pid " reads eechannel's pid from eech.B.str, 3) "----------------->" sends a signal SIGUSR1 to that pid, 4) " eeg.A.str --->" ...and when eechannel receives a SIGUSR1 it reads the contents of eech.A.str and sends that to its spawned shell. Actually there's something else - how these programs are started. If we run just "xterm", it behaves in its default way, and runs a shell. But if we run "xterm -e eechannel A /bin/sh" then xterm runs "eechannel A /bin/sh" instead of just "/bin/sh"; and "eechannel A /bin/sh" runs "/bin/sh". Also, evaluating this (find-bgprocess "xterm") from Emacs runs an xterm, which runs a shell; evaluating either of these sexps, (find-bgprocess "xterm -e eechannel A /bin/sh") (find-bgprocess "xterm -e eechannel A $SHELL") runs an xterm, which runs the "eechannel" script, which runs a shell. The sexp (eexterm "A") is a shorthand for the one using "$SHELL". Also, note that "eechannel A ..." saves its pid into "eech.A.pid". The diagram that shows what happens when we run `(eexterm "A")' is this one, below - note that the four arrows of the sending protocol are not shown. +------------+ | | | X | | | +------------+ / ^ \ ^ v / v \ +-----------+ +-------------+ | | initiates | | | emacs |:::::::::::>| xterm | | | | | +-----------+ +-------------+ :: | ^ \/ v | +-------------+ | | eeg.A.pid <-- | eechannel A | | | +-------------+ :: | ^ \/ v | +-------------+ | | | sh | | | +-------------+5. Downloading and testing eechannel
Here I'll suppose that the directory "~/bin/" exists and is in your PATH. Run this to download the "eechannel" script and make it executable: * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) cd ~/bin/ # See: http://anggtwu.net/bin/eechannel.html wget -n http://anggtwu.net/bin/eechannel chmod 755 eechannel Now let's test - from Emacs - if a local copy of "eechannel" exists, is in your PATH, and can be executed (remember that it needs Expect). Calling "eechannel" with not enough arguments should yield a nice error message. (find-fline "~/bin/eechannel") (find-sh "~/bin/eechannel") (find-sh "eechannel") It uses the environment variable EEVTMPDIR to decide where the temporary files - ee.CHANNELAME.pid, ee.CHANNELNAME.str - will be, so let's check that this directory exists: (getenv "EEVTMPDIR") (find-fline "$EEVTMPDIR/") Running eechannel with a first argument "-v" should show a lot of info, and then save the pid at eech.CHANNELAME.pid and run the given command. Let's try with a command that exits immediately. (find-sh "eechannel -v A /bin/true") (find-fline "$EEVTMPDIR/" "eech.A.pid") Now let's verify - using just eepitch, without xterm at this moment - whether eechannel is being able to receive signals. In the eepitch script below, two `(find-sh0 "kill ...")'s should each make the controlled program receive a command - at the first "kill" an "echo 2", at the second an "echo 3". * (eepitch-comint "A" "eechannel -v A /bin/sh") * (eepitch-kill) * (eepitch-comint "A" "eechannel -v A /bin/sh") echo 1 * (find-sh0 " cat $EEVTMPDIR/eech.A.pid") * (find-sh0 "echo 'echo 2' > $EEVTMPDIR/eech.A.str") * (find-sh0 " cat $EEVTMPDIR/eech.A.str") * (find-sh0 "kill -USR1 $(cat $EEVTMPDIR/eech.A.pid)") * * (find-sh0 "echo 'echo 3' > $EEVTMPDIR/eech.A.str") * (find-sh0 "kill -USR1 $(cat $EEVTMPDIR/eech.A.pid)") echo 4 If the eepitched shell did run "echo 1", "echo 2", "echo 3", "echo 4", then things are working - "echo 1" and "echo 4" were sent through eepitch, but "echo 3" and "echo 4" were sent though channels. Now let's check if we can run an xterm, and if it can run an eechannel-ed shell instead of a plain shell. Check if the second sexp above starts an xterm, displays the info from "eechannel -v", and then runs a shell: (find-bgprocess "xterm") (find-bgprocess "xterm -e eechannel -v A $SHELL") (find-bgprocess "xterm -e eechannel A $SHELL") if that worked, run the four sexps below with <f8>, * (find-sh0 "echo 'echo 1' > $EEVTMPDIR/eech.A.str") * (find-sh0 "kill -USR1 $(cat $EEVTMPDIR/eech.A.pid)") * (find-sh0 "echo 'echo 2' > $EEVTMPDIR/eech.A.str") * (find-sh0 "kill -USR1 $(cat $EEVTMPDIR/eech.A.pid)") and check if the eechannel-ed shell in the xterm has received the commands "echo 1" and "echo 2". If it did, try the higher-level version below - but use <f9>s on each line instead of the <f8>s: * (eexterm "A") echo 3 echo 4 If that worked, we're done. =)6. Several xterms
http://anggtwu.net/eev-current/anim/channels.anim.html * (eexterm "A") * (eexterm "B") # listen on port 1234 netcat -l -p 1234 * (eexterm "A") # Send things to port 1234 (on localhost) { echo hi sleep 1 echo bye sleep 1 } | netcat -q O localhost 1234 [NOT DONE YET. The rest is (recyclable) garbage.] (Old stuff:) emacs runs: (find-bgprocess "xterm -T 'channel A' -e eegchannel A /bin/sh") xterm runs: eegchannel A /bin/sh eegchannel saves its pid at ~/.eev/eeg.A.pid and runs: /bin/sh At this point the xterm is running a shell that is "listening on channel A"; eegchannel pretends to be transparent and passes all characters and signals in the vertical arrows transparently - but when it receives a certain signal (a SIGUSR1) it reads the contents from the file ~/.eev/eeg.A.str and passes it to the shell, as if those characters were coming from the xterm - i.e., as if the used had typed them. Here are the details of this "protocol": when we type F9 on a line containing "echo foo", emacs saves the string "echo foo\n" into ~/.eev/A.str, emacs reads the pid of eegchannel from ~/.eev/eeg.A.pid (say, "1022"), emacs runs "kill -USR1 1022", eegchannel reads the string from ~/.eev/A.str, and sends it to the shell. NOTE: one frequent question is: "why are you using two normal files and SIGUSR1s for the communication between emacs and eegchannel, instead of making them talk through a fifo?" - the answer is: try to explain the details of fifos - including creation - to someone who knows little about the inner workings of a *NIX kernel and who is uncomfortable to accept another kind of black box... The way with two files and SIGUSR1s is simpler, works well enough, and I've been able to write all the code for it in a few hours - and I still don't know enough about fifos to implement a fifo version of this. and another one, that was mostly used to control external programs running in dedicated xterms. You can see an animation demonstrating it - and an explanation of what each line does - here: http://anggtwu.net/eev-current/anim/channels.anim.html http://anggtwu.net/eev-current/doc/shot-f9.pngHow to set it up
[To be written] ssh edrx@other.machine eegchannel A ssh edrx@other.machine (find-prepared-intro "\n`ee'\n") (find-eev "anim/channels.anim")