Controlling Grail Remotely


By using your startup file you can -- at least under Unix -- remotely control your Grail browser. This can be used, for example, so that clicking on a URL in an Emacs window automatically instructs Grail to load that page. To do this you need to do three things:
  1. Turn on the remote control callbacks in Grail
  2. Write a little script that sends remote control commands from outside Grail.
  3. Hook this script up to Emacs.
You should also take a look at a note about security and a note about some problems you might encounter.

The Remote Control Protocol

N.B. The Common Client Interface (CCI) should be used instead of this protocol!

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.

Turning on the remote control callbacks

If all you want to do is enable the ability to load URLs from a remote program, you can use the simple interface. This sets up automatic callbacks so that Grail will recognize command strings of the following form:
LOAD http://some.place.com/a/url/of/interest.html
Load the specified URL into the browser window. If more than one browser window is open, then the last to be opened is used.
LOADNEW http://some.place.com/a/url/of/interest.html
Load the specified URL into a new browser window.

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

The external script

The external script is slightly more complicated. To write this, you need to know how to rendezvous with Grail. The remote control module creates a Unix domain socket on which it listens for the simple string-based protocol described above. By default, it creates this file in $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!

Hooking it up to Emacs

I use remote control so that when I'm reading a message in Emacs, either from email or Usenet, I can just click the mouse over a URL and have Grail automatically load it. Given that you've enabled remote control, and have the 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

Security

Security is rather minimal. First, a Unix domain socket is used instead of an Inet socket, so only users with access to your machine can send commands to Grail. Second, the socket is placed inside a directory which has only user read, write, and execute permission. So only someone with access to that directory can write to your socket. Third, Grail cannot be made to execute arbitrary Python code (hey! that's what applets are for) so the remote control only has as much access to Grail as you permit.

Limitations and Problems

Only one Grail can be remotedly controlled at a time, so if you think you might be starting up more than one Grail process, your 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.