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
by the 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 RemoteControl.register(cmdstr, callback)
where cmdstr
is the command to dispatch on (the first
word), and callback
is a function taking three arguments as
described above. There is also a corresponding
unregister
function.
LOAD http://some.place.com/a/url/of/interest.html
LOADNEW http://some.place.com/a/url/of/interest.html
N.B. This is not integrated with Frames yet. The LOAD
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
$TMPDIR/.grail-unix/$USER-$DISPLAY
, e.g.
/tmp/.grail-unix/jdoe-:0
. It sets the file permissions
on the .grail-unix
directory 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
xhost
/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
SampleGrailDir/user
called
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.py
script 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.py
file 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
raise 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.