Grail's remote control is based on a very simple string based
protocol. You send string commands to Grail, and it executes
functions bound to those commands. The first word of the string is
the command to execute, and any subsequent text (after the first tab
or space character after the command) is the argument to the command.
Callbacks can be registered with the remote control system, and a
simple match is made on the command. The callback is called with three
arguments, the command, the argument string, and the socket returned
accept(). The callback can use this socket to
respond to the caller.
The most useful
callbacks are already defined, you only need to
enable them. You can register any callback you
want by using
cmdstr is the command to dispatch on (the first
callback is a function taking three arguments as
described above. There is also a corresponding
N.B. This is not integrated with Frames yet. The
commands should take an optional target argument!
To enable the simple interface, just add the following to your Grail startup file:
# Grail initialization file # Turn on remote control. Ignore error that get's raised if some # other Grail is being remote controlled. import RemoteControl RemoteControl.register_loads() try: RemoteControl.start() except RemoteControl.ClashError: pass
/tmp/.grail-unix/jdoe-:0. It sets the file permissions on the
.grail-unixdirectory so that only the user can write to it.
N.B. We could have used Tcl's
send command here but
we didn't. First,
send security is based on
xauth and most people don't use these
correctly. Second, there were questions as to whether
send still works with cross-platform support as of Tk
4.1. Not that the current mechanism works any better for non-Unix
systems without Unix domain sockets. The long term solution might be
an ILU based rendezvous.
I'm not going to go into all the details of writing this script. The
Grail source distribution contains a file in
rcgrail.py which will server as
a useful template. It may work right out of the box, but
modifications are left as an exercise for the reader!
rcgrail.pyscript in your
~/.grail/user/directory, you can just add a little Emacs magic for the final bit of glue. Caveats: the following has only been tested with XEmacs 19.13, using stock VM 5.95 and GNUS 4.1.3. YMMV!
For VM, use:
(setq vm-url-browser "~/.grail/user/rcgrail.py")
For GNUS, use:
(defun baw:send-url-to-grail (url) (message "Sending URL to Grail...") (save-excursion (set-buffer (get-buffer-create "*Shell Command Output*")) (erase-buffer) ;; don't worry about this failing... (call-process "~/.grail/user/rcgrail.py" nil 0 nil url) (message "Sending URL to Grail... done"))) (setq gnus-button-url 'baw:send-url-to-grail ; GNUS 5 highlight-headers-follow-url-function 'baw:send-url-to-grail) ; GNUS 4
grailrc.pyfile should catch
RemoteControl.ClashError, usually just passed through.
Normally, whenever Grail exits for any reason, the rendezvous
socket should be deleted, so that the next time you start up Grail, a
new socket for remote control will be created. The remote control
subsystem uses an exit handler to
ensure this, even if Grail exits due to some unexpected condition
(e.g. an uncaught exception occuring). However, if the Python
interpreter actually core dumps, the exit handler will never get run,
so the socket could hang around. This will cause subsequent Grails to
RemoteControl.ClashError. The remote control
system is a little smarter than that though. If it finds the socket
file, it attempts to communicate with the Grail process on the other
end, by sending it a
PING NOACK command string. If the
socket connection succeeds, then it knows that the other Grail is
alive and well (so it raises the
RemoteControl.ClashError). If the socket connection
fails, then it knows the other Grail process must have died so it
absconds the socket file for its own use.