]> code.delx.au - gnu-emacs/blobdiff - lisp/browse-url.el
(backup-extract-version-start): New variable.
[gnu-emacs] / lisp / browse-url.el
index 542e6bb934e25d8fb7ddbe6f4a124f46fc95390b..2f6012c68dfd3d1048a307393593a864852db10f 100644 (file)
@@ -1,5 +1,6 @@
 ;;; browse-url.el --- ask a WWW browser to load a URL
-;; Copyright 1995 Free Software Foundation, Inc.
+
+;; Copyright 1995, 1996 Free Software Foundation, Inc.
 
 ;; Author: Denis Howe <dbh@doc.ic.ac.uk>
 ;; Maintainer: Denis Howe <dbh@doc.ic.ac.uk>
 ;; This file is part of GNU Emacs.
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published
-;; by the Free Software Foundation; either version 2, or (at your
-;; option) any later version.
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
 
-;; GNU Emacs is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-;; General Public License for more details.
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Commentary:
 
 ;; The latest version of this package should be available from
 ;; browse-url-cci        XMosaic     2.5
 ;; browse-url-w3         w3          0
 ;; browse-url-iximosaic  IXI Mosaic  ?
+;; browse-url-lynx-*    Lynx        0
+;; browse-url-grail      Grail       0.3b1
 
 ;; Note that versions of Netscape before 1.1b1 did not have remote
-;; control.  <URL:http://home.netscape.com/newsref/std/x-remote.html>
-;; and <URL:http://home.netscape.com/info/APIs/>.
+;; control.  <URL:http://www.netscape.com/newsref/std/x-remote.html>
+;; and <URL:http://www.netscape.com/info/APIs/>.
 
 ;; Netscape can cache Web pages so it may be necessary to tell it to
-;; reload the current page if it has changed (eg. if you have edited
+;; reload the current page if it has changed (e.g. if you have edited
 ;; it).  There is currently no perfect automatic solution to this.
 
 ;; Netscape allows you to specify the id of the window you want to
 ;; (find-file-at-point) <URL:ftp://cs.ucsd.edu:/pub/mic/>.  The huge
 ;; hyperbole package also contains similar functions.
 
+;; Grail is the freely available WWW browser implemented in Python, a
+;; cool object-oriented freely available interpreted language.  Grail
+;; 0.3b1 was the first version to have remote control as distributed.
+;; For more information on Grail see
+;; <URL:http://monty.cnri.reston.va.us/> and for more information on
+;; Python see <url:http://www.python.org/>.  Grail support in
+;; browse-url.el written by Barry Warsaw <bwarsaw@python.org>.
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Help!
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Customisation (~/.emacs)
 
-;; To see what variables are available for customization, type `M-x
-;; set-variable browse-url TAB'.
+;; To see what variables are available for customization, type
+;; `M-x set-variable browse-url TAB'.
 
-;; To bind the browse-url commands to keys with the `C-c u' prefix:
-;;      (global-set-key "\C-cu." 'browse-url-at-point)
-;;      (global-set-key "\C-cub" 'browse-url-of-buffer)
-;;      (global-set-key "\C-cuf" 'browse-url-of-file)
+;; Bind the browse-url commands to keys with the `C-c C-z' prefix
+;; (as used by html-helper-mode):
+;;     (global-set-key "\C-c\C-z." 'browse-url-at-point)
+;;     (global-set-key "\C-c\C-zb" 'browse-url-of-buffer)
+;;     (global-set-key "\C-c\C-zu" 'browse-url)
+;;     (global-set-key "\C-c\C-zv" 'browse-url-of-file)
 ;;      (add-hook 'dired-mode-hook
-;;                (lambda ()
-;;                  (local-set-key "\C-cuf" 'browse-url-of-dired-file))))
-;;      (if (boundp 'browse-url-browser-function)
-;;          (global-set-key "\C-cuu" browse-url-browser-function)
-;;        (eval-after-load
-;;         "browse-url"
-;;         '(global-set-key "\C-cuu" browse-url-browser-function)))
-
-;; To use the Emacs w3 browser when not running under X11:
-;;      (if (not (eq window-system 'x))
+;;               (function (lambda ()
+;;                           (local-set-key "\C-c\C-zf" 'browse-url-of-dired-file))))
+
+;; Browse URLs in mail messages by clicking mouse-2:
+;;     (add-hook 'rmail-mode-hook (function (lambda () ; rmail-mode startup
+;;       (define-key rmail-mode-map [mouse-2] 'browse-url-at-mouse))))
+
+;; Browse URLs in Usenet messages by clicking mouse-2:
+;;     (eval-after-load "gnus"
+;;       '(define-key gnus-article-mode-map [mouse-2] 'browse-url-at-mouse))
+
+;; Use the Emacs w3 browser when not running under X11:
+;;     (or (eq window-system 'x)
 ;;          (setq browse-url-browser-function 'browse-url-w3))
 
 ;; To always save modified buffers before displaying the file in a browser:
 ;;      (setq browse-url-save-file t)
 
-;; To get round the Netscape caching problem, you could try either of
-;; the following (but not both).  EITHER write-file in
-;; html-helper-mode makes Netscape reload document:
+;; To get round the Netscape caching problem, you could EITHER have
+;; write-file in html-helper-mode make Netscape reload the document:
 ;;
 ;;      (autoload 'browse-url-netscape-reload "browse-url"
 ;;        "Ask a WWW browser to redisplay the current file." t)
 ;;                                t))                   ; => file written by hook
 ;;                             t))))                    ; append to l-w-f-hooks
 ;;
-;; [Does this work for html-mode too?]
-;;
-;; OR browse-url-of-file ask Netscape to load and then reload the
+;; OR have browse-url-of-file ask Netscape to load and then reload the
 ;; file:
 ;;
 ;;      (add-hook 'browse-url-of-file-hook 'browse-url-netscape-reload)
 
-;; You may also want to customise browse-url-netscape-arguments, eg.
-;;
+;; You may also want to customise browse-url-netscape-arguments, e.g.
 ;;      (setq browse-url-netscape-arguments '("-install"))
 ;;
 ;; or similarly for the other browsers. 
 ;;      Use start-process instead of start-process-shell-command.
 
 ;; 0.03 06 Apr 1995
-;;      Add browse-url-netscape-reload, browse-url-netscape-command.
+;;     Add browse-url-netscape-reload, browse-url-netscape-send.
 ;;      browse-url-of-file save file option.
 
 ;; 0.04 08 Apr 1995
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Code:
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Variables
+
+(eval-when-compile (require 'dired))
+
+(defvar browse-url-path-regexp
+  "[^]\t\n \"'()<>[^`{}]*[^]\t\n \"'()<>[^`{}.,;]+"
+  "A regular expression probably matching the host, path or e-mail
+part of a URL.")
+
+(defvar browse-url-short-regexp
+  (concat "[-A-Za-z0-9.]+" browse-url-path-regexp)
+  "A regular expression probably matching a URL without an access scheme.
+Hostname matching is stricter in this case than for
+``browse-url-regexp''.")
+
 (defvar browse-url-regexp
-  "\\(https?://\\|ftp://\\|gopher://\\|telnet://\\|wais://\\|file:/\\|s?news:\\|mailto:\\)[^]\t\n \"'()<>[^`{}]*[^]\t\n \"'()<>[^`{}.,;]+"
-  "A regular expression probably matching a URL.")
+  (concat
+   "\\(https?://\\|ftp://\\|gopher://\\|telnet://\\|wais://\\|file:/\\|s?news:\\|mailto:\\)"
+   browse-url-path-regexp)
+  "A regular expression probably matching a complete URL.")
+
+;;;###autoload
+(defgroup browse-url nil
+  "Use a web browser to look at a URL."
+  :group 'emacs)
 
-(defvar browse-url-browser-function
+;;;###autoload
+(defcustom browse-url-browser-function
   'browse-url-netscape
   "*Function to display the current buffer in a WWW browser.
-Used by the `browse-url-at-point', `browse-url-at-mouse', and
-`browse-url-of-file' commands.")
-
-(defvar browse-url-netscape-arguments nil
-  "*A list of strings to pass to Netscape as arguments.")
-
-(defvar browse-url-new-window-p nil
+This is used by the `browse-url-at-point', `browse-url-at-mouse', and
+`browse-url-of-file' commands.
+The function should take one argument, an URL."
+  :type 'function
+  :group 'browse-url)
+
+(defcustom browse-url-netscape-program "netscape"
+  "*The name by which to invoke Netscape."
+  :type 'string
+  :group 'browse-url)
+
+(defcustom browse-url-netscape-arguments nil
+  "*A list of strings to pass to Netscape as arguments."
+  :type '(repeat (string :tag "Argument"))
+  :group 'browse-url)
+
+(defcustom browse-url-netscape-startup-arguments browse-url-netscape-arguments
+  "*A list of strings to pass to Netscape when it starts up.
+Defaults to the value of `browse-url-netscape-arguments' at the time
+browse-url is loaded."
+  :type '(repeat (string :tag "Argument"))
+  :group 'browse-url)
+
+(defcustom browse-url-new-window-p nil
   "*If non-nil, always open a new browser window.
 Passing an interactive argument to \\[browse-url-netscape] or
 \\[browse-url-cci] reverses the effect of this variable.  Requires
-Netscape version 1.1N or later or XMosaic version 2.5 or later.")
+Netscape version 1.1N or later or XMosaic version 2.5 or later."
+  :type 'boolean
+  :group 'browse-url)
 
-(defvar browse-url-mosaic-arguments nil
-  "*A list of strings to pass to Mosaic as arguments.")
+(defcustom browse-url-mosaic-arguments nil
+  "*A list of strings to pass to Mosaic as arguments."
+  :type '(repeat (string :tag "Argument"))
+  :group 'browse-url)
 
 (defvar browse-url-filename-alist
   '(("^/+" . "file:/"))
   "An alist of (REGEXP . STRING) pairs.
 Any substring of a filename matching one of the REGEXPs is replaced by
 the corresponding STRING.  All pairs are applied in the order given.
+The default value prepends `file:' to any path beginning with `/'.
 Used by the `browse-url-of-file' command.")
 
 (defvar browse-url-save-file nil
@@ -298,7 +356,7 @@ file rather than displaying a cached copy.")
 (defvar browse-url-usr1-signal
   (if (and (boundp 'emacs-major-version)
            (or (> emacs-major-version 19) (>= emacs-minor-version 29)))
-      'SIGUSR1
+      'SIGUSR1 ; Why did I think this was in lower case before?
     30)                                 ; Check /usr/include/signal.h.
   "The argument to `signal-process' for sending SIGUSR1 to XMosaic.
 Emacs 19.29 accepts 'SIGUSR1, earlier versions require an integer
@@ -309,60 +367,46 @@ which is 30 on SunOS and 16 on HP-UX and Solaris.")
 This can be any number between 1024 and 65535 but must correspond to
 the value set in the browser.")
 
+(defvar browse-url-CCI-host "localhost"
+  "*Host to access XMosaic via CCI.
+This should be the host name of the machine running XMosaic with CCI
+enabled.  The port number should be set in `browse-url-CCI-port'.")
+
+(defvar browse-url-temp-file-name nil)
+(make-variable-buffer-local 'browse-url-temp-file-name)
+
+(defvar browse-url-temp-file-list '())
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; URL input
 
-;; thingatpt.el doesn't work for complex regexps.
+;; thingatpt.el doesn't work for complex regexps
 
 (defun browse-url-url-at-point ()
   "Return the URL around or before point.
-Then search backwards for the start of a URL.  If no URL found, return
-the empty string."
-  (if (or (looking-at browse-url-regexp)        ; Already at start
-          (let ((eol (save-excursion (end-of-line) (point))))
-            ;; Search forwards for the next URL or end of line in case
-            ;; we're in the middle of one.
-            (and (re-search-forward browse-url-regexp eol 'lim)
-                 (goto-char (match-beginning 0)))
-            ;; Now back to where we started or earlier.
-            (re-search-backward browse-url-regexp nil t)))
-      (buffer-substring (match-beginning 0) (match-end 0))
-    ""))                                ; No match
-
-;; Todo: restrict to around or immediately before point.  Expand bare
-;; hostname to URL.
-
-(defun browse-url-interactive-arg (&optional prompt)
-  "Read a URL from the minibuffer, optionally prompting with PROMPT.
-Default to the URL at or before point.  If bound to a mouse button,
-set point to the position clicked.  Return the result as a list for
-use in `interactive'."
+Search backwards for the start of a URL ending at or after 
+point.  If no URL found, return the empty string.
+A file name is also acceptable, and `http://' will be prepended to it."
+  (or (thing-at-point 'url)
+      (let ((file (thing-at-point 'filename)))
+       (if file (concat "http://" file)))
+      ""))
+
+;; Having this as a separate function called by the browser-specific
+;; functions allows them to be stand-alone commands, making it easier
+;; to switch between browsers.
+
+(defun browse-url-interactive-arg (prompt)
+  "Read a URL from the minibuffer, prompting with PROMPT.
+Default to the URL at or before point.  If invoke with a mouse button,
+set point to the position clicked first.  Return a list for use in
+`interactive' containing the URL and browse-url-new-window-p or its
+negation if a prefix argument was given."
   (let ((event (elt (this-command-keys) 0)))
     (and (listp event) (mouse-set-point event)))
-  (list (read-string (or prompt "URL: ") (browse-url-url-at-point))))
-
-;;;###autoload
-(defun browse-url-at-point ()
-  "Ask a WWW browser to load the URL at or before point.
-The URL is loaded according to the value of `browse-url-browser-function'."
-  (interactive)
-  (funcall browse-url-browser-function (browse-url-url-at-point)))
-
-;;;###autoload
-(defun browse-url-at-mouse (event)
-  "Ask a WWW browser to load a URL clicked with the mouse.
-The URL is the one around or before the position of the mouse click
-but point is not changed.  The URL is loaded according to the value of
-`browse-url-browser-function'."
-  (interactive "e")
-  (save-excursion
-    (let ((posn (event-start event)))
-      (set-buffer (window-buffer (posn-window posn)))
-      (goto-char (posn-point posn))
-      (let ((url (browse-url-url-at-point)))
-        (if (string-equal url "")
-            (error "No URL found"))
-        (funcall browse-url-browser-function url)))))
+  (list (read-string prompt (browse-url-url-at-point))
+       (not (eq (null browse-url-new-window-p)
+                (null current-prefix-arg)))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Browse current buffer
@@ -371,15 +415,13 @@ but point is not changed.  The URL is loaded according to the value of
 (defun browse-url-of-file (&optional file)
   "Ask a WWW browser to display FILE.
 Display the current buffer's file if FILE is nil or if called
-interactively.  Turn the filename into a URL by performing
-replacements given in variable `browse-url-filename-alist'.  Pass the
-URL to a browser using variable `browse-url-browser-function' then run
-`browse-url-of-file-hook'."
+interactively.  Turn the filename into a URL with function
+browse-url-file-url.  Pass the URL to a browser using variable
+`browse-url-browser-function' then run `browse-url-of-file-hook'."
   (interactive)
-  (setq file
         (or file
-            (buffer-file-name)
-            (error "Current buffer has no file")))
+      (setq file (buffer-file-name))
+      (error "Current buffer has no file"))
   (let ((buf (get-file-buffer file)))
     (if buf
         (save-excursion
@@ -392,48 +434,54 @@ URL to a browser using variable `browse-url-browser-function' then run
 
 (defun browse-url-file-url (file)
   "Return the URL corresponding to FILE.
-Uses variable `browse-url-filename-alist' to map filenames to URLs."
+Use variable `browse-url-filename-alist' to map filenames to URLs.
+Convert EFS file names of the form /USER@HOST:PATH to ftp://HOST/PATH."
+  ;; URL-encode special chars, do % first
+  (let ((s 0))
+    (while (setq s (string-match "%" file s))
+      (setq file (replace-match "%25" t t file)
+           s (1+ s))))
+  (while (string-match "[*\"()',=;? ]" file)
+    (let ((enc (format "%%%x" (aref file (match-beginning 0)))))
+      (setq file (replace-match enc t t file))))
   (let ((maps browse-url-filename-alist))
     (while maps
       (let* ((map (car maps))
              (from-re (car map))
              (to-string (cdr map)))
         (setq maps (cdr maps))
-        (if (string-match from-re file)
-            (setq file (concat (substring file 0 (match-beginning 0))
-                               to-string
-                               (substring file (match-end 0))))))))
+       (and (string-match from-re file)
+            (setq file (replace-match to-string t t file))))))
+  ;; Check for EFS path
+  (and (string-match "^/\\([^:@]+@\\)?\\([^:]+\\):/*" file)
+       (setq file (concat "ftp://"
+                         (substring file (match-beginning 2) (match-end 2))
+                         "/" (substring file (match-end 0)))))
   file)
 
-(defvar browse-url-temp-file-name nil)
-(make-variable-buffer-local 'browse-url-temp-file-name)
-
-(defvar browse-url-temp-file-list '())
-
 ;;;###autoload
 (defun browse-url-of-buffer (&optional buffer)
   "Ask a WWW browser to display BUFFER.
 Display the current buffer if BUFFER is nil."
   (interactive)
   (save-excursion
-    (set-buffer (or buffer (current-buffer)))
+    (and buffer (set-buffer buffer))
     (let ((file-name
            (or buffer-file-name
                (and (boundp 'dired-directory) dired-directory))))
-      (if (null file-name)
-          (progn
-            (if (null browse-url-temp-file-name)
+      (or file-name
                 (progn
+           (or browse-url-temp-file-name
                   (setq browse-url-temp-file-name
                         (make-temp-name
                          (expand-file-name (buffer-name)
-                                           (or (getenv "TMPDIR") "/tmp"))))
-                  (setq browse-url-temp-file-list
+                                        (or (getenv "TMPDIR") "/tmp")))
+                     browse-url-temp-file-list
                         (cons browse-url-temp-file-name
-                              browse-url-temp-file-list))))
-            (write-region (point-min) (point-max) browse-url-temp-file-name
-                          nil 'no-message)))
-      (browse-url-of-file (or file-name browse-url-temp-file-name)))))
+                           browse-url-temp-file-list)))
+           (setq file-name browse-url-temp-file-name)
+           (write-region (point-min) (point-max) file-name nil 'no-message)))
+      (browse-url-of-file file-name))))
 
 (defun browse-url-delete-temp-file (&optional temp-file-name)
   ;; Delete browse-url-temp-file-name from the file system and from
@@ -466,8 +514,77 @@ Display the current buffer if BUFFER is nil."
   (browse-url-of-file (dired-get-filename)))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Browser-specific functions
+;; Browser-independant commands
+
+;; A generic command to call the current b-u-browser-function
 
+(defun browse-url (&rest args)
+  "Ask a WWW browser to load URL.
+Prompts for a URL, defaulting to the URL at or before point.  Variable
+`browse-url-browser-function' says which browser to use."
+  (interactive (browse-url-interactive-arg "URL: "))
+  (apply browse-url-browser-function args))
+
+;;;###autoload
+(defun browse-url-at-point ()
+  "Ask a WWW browser to load the URL at or before point.
+Doesn't let you edit the URL like browse-url.  Variable
+`browse-url-browser-function' says which browser to use."
+  (interactive)
+  (funcall browse-url-browser-function (browse-url-url-at-point)))
+
+;; Define these if not already defined (XEmacs compatibility)
+
+(eval-and-compile
+  (or (fboundp 'event-buffer)
+      (defun event-buffer (event)
+       (window-buffer (posn-window (event-start event))))))
+
+(eval-and-compile
+  (or (fboundp 'event-point)
+      (defun event-point (event)
+       (posn-point (event-start event)))))
+
+;;;###autoload
+(defun browse-url-at-mouse (event)
+  "Ask a WWW browser to load a URL clicked with the mouse.
+The URL is the one around or before the position of the mouse click
+but point is not changed.  Doesn't let you edit the URL like
+browse-url.  Variable `browse-url-browser-function' says which browser
+to use."
+  (interactive "e")
+  (save-excursion
+    (set-buffer (event-buffer event))
+    (goto-char (event-point event))
+    (let ((url (browse-url-url-at-point)))
+      (if (string-equal url "")
+         (error "No URL found"))
+      (funcall browse-url-browser-function url))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Browser-specific commands
+
+;; --- Netscape ---
+
+;; Put the correct DISPLAY value in the environment for Netscape
+;; launched from multi-display Emacs.
+
+(defun browse-url-process-environment ()
+  (let* ((device (and (fboundp 'selected-device)
+                     (fboundp 'device-connection)
+                      (selected-device)))
+         (display (and device (fboundp 'device-type)
+                       (eq (device-type device) 'x)
+                       (not (equal (device-connection device)
+                                   (getenv "DISPLAY"))))))
+    (if display
+        ;; Attempt to run on the correct display
+        (cons (concat "DISPLAY=" (device-connection device))
+              process-environment)
+      process-environment)))
+
+
+;;;###autoload
 (defun browse-url-netscape (url &optional new-window)
   "Ask the Netscape WWW browser to load URL.
 
@@ -481,36 +598,52 @@ the effect of browse-url-new-window-p.
 
 When called non-interactively, optional second argument NEW-WINDOW is
 used instead of browse-url-new-window-p."
-
-  (interactive (append (browse-url-interactive-arg "Netscape URL: ")
-                       (list (not (eq (null browse-url-new-window-p)
-                                      (null current-prefix-arg))))))
-  (or (zerop
-       (apply 'call-process "netscape" nil nil nil
-              (append browse-url-netscape-arguments
-                      (if new-window '("-noraise"))
-                      (list "-remote" 
-                            (concat "openURL(" url 
-                                    (if new-window ",new-window")
-                                    ")")))))
-      (progn                            ; Netscape not running - start it
-        (message "Starting Netscape...")
-        (apply 'start-process "netscape" nil "netscape"
-               (append browse-url-netscape-arguments (list url)))
-        (message "Starting Netscape...done"))))
+  (interactive (browse-url-interactive-arg "Netscape URL: "))
+  ;; URL encode any commas in the URL
+  (while (string-match "," url)
+    (setq url (replace-match "%2C" t t url)))
+  (let* ((process-environment (browse-url-process-environment))
+         (process (apply 'start-process
+                       (concat "netscape " url) nil
+                       browse-url-netscape-command
+               (append browse-url-netscape-arguments
+                       (if new-window '("-noraise"))
+                       (list "-remote" 
+                             (concat "openURL(" url 
+                                     (if new-window ",new-window")
+                                             ")"))))))
+    (set-process-sentinel process
+       (list 'lambda '(process change)
+            (list 'browse-url-netscape-sentinel 'process url)))))
+
+(defun browse-url-netscape-sentinel (process url)
+  "Handle a change to the process communicating with Netscape."
+  (or (eq (process-exit-status process) 0)
+      (let* ((process-environment (browse-url-process-environment)))
+       ;; Netscape not running - start it
+           (message "Starting Netscape...")
+       (apply 'start-process (concat "netscape" url) nil
+              browse-url-netscape-command
+              (append browse-url-netscape-startup-arguments (list url))))))
 
 (defun browse-url-netscape-reload ()
   "Ask Netscape to reload its current document."
   (interactive)
-  (browse-url-netscape-command "reload"))
+  (browse-url-netscape-send "reload"))
 
-(defun browse-url-netscape-command (command)
+(defun browse-url-netscape-send (command)
   "Send a remote control command to Netscape."
-  (apply 'start-process "netscape" nil "netscape"
+  (let* ((process-environment (browse-url-process-environment)))
+    (apply 'start-process "netscape" nil
+           browse-url-netscape-command
          (append browse-url-netscape-arguments
-                 (list "-remote" command))))
+                   (list "-remote" command)))))
+
+;; --- Mosaic ---
 
-(defun browse-url-mosaic (url)
+;;;###autoload
+(defun browse-url-mosaic (url &optional new-window)
+  ;; new-window ignored
   "Ask the XMosaic WWW browser to load URL.
 Default to the URL around or before point."
   (interactive (browse-url-interactive-arg "Mosaic URL: "))
@@ -530,10 +663,11 @@ Default to the URL around or before point."
           (save-buffer)
           (kill-buffer nil)
           ;; Send signal SIGUSR to Mosaic
+         (message "Signalling Mosaic...")
           (signal-process pid browse-url-usr1-signal)
-          (message "Signal sent to Mosaic")
           ;; Or you could try:
           ;; (call-process "kill" nil 0 nil "-USR1" (int-to-string pid))
+         (message "Signalling Mosaic...done")
           )
       ;; Mosaic not running - start it
       (message "Starting Mosaic...")
@@ -541,6 +675,30 @@ Default to the URL around or before point."
              (append browse-url-mosaic-arguments (list url)))
       (message "Starting Mosaic...done"))))
 
+;; --- Grail ---
+
+;;;###autoload
+(defvar browse-url-grail
+  (concat (or (getenv "GRAILDIR") "~/.grail") "/user/rcgrail.py")
+  "*Location of Grail remote control client script `rcgrail.py'.
+Typically found in $GRAILDIR/rcgrail.py, or ~/.grail/user/rcgrail.py.")
+
+;;;###autoload
+(defun browse-url-grail (url)
+  "Ask the Grail WWW browser to load URL.
+Default to the URL around or before point.  Runs the program in the
+variable `browse-url-grail'."
+  (interactive (browse-url-interactive-arg "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 browse-url-grail nil 0 nil url)
+    (message "Sending URL to Grail... done")))
+
+;; --- Mosaic using CCI ---
+
 (defun browse-url-cci (url &optional new-window)
   "Ask the XMosaic WWW browser to load URL.
 Default to the URL around or before point.
@@ -556,11 +714,9 @@ the effect of browse-url-new-window-p.
 
 When called non-interactively, optional second argument NEW-WINDOW is
 used instead of browse-url-new-window-p."
-  (interactive (append (browse-url-interactive-arg "Mosaic URL: ")
-                       (list (not (eq (null browse-url-new-window-p)
-                                      (null current-prefix-arg))))))
+  (interactive (browse-url-interactive-arg "Mosaic URL: "))
   (open-network-stream "browse-url" " *browse-url*"
-                       "localhost" browse-url-CCI-port)
+                      browse-url-CCI-host browse-url-CCI-port)
   ;; Todo: start browser if fails
   (process-send-string "browse-url"
                        (concat "get url (" url ") output "
@@ -568,19 +724,58 @@ used instead of browse-url-new-window-p."
   (process-send-string "browse-url" "disconnect\r\n")
   (delete-process "browse-url"))
 
-(defun browse-url-iximosaic (url)
+;; --- IXI Mosaic ---
+
+;;;###autoload
+(defun browse-url-iximosaic (url &optional new-window)
+  ;; new-window ignored
   "Ask the IXIMosaic WWW browser to load URL.
 Default to the URL around or before point."
   (interactive (browse-url-interactive-arg "IXI Mosaic URL: "))
   (start-process "tellw3b" nil "tellw3b"
                  "-service WWW_BROWSER ixi_showurl " url))
 
-(defun browse-url-w3 (url)
+;; --- W3 ---
+
+;;;###autoload
+(defun browse-url-w3 (url &optional new-window)
+  ;; new-window ignored
   "Ask the w3 WWW browser to load URL.
 Default to the URL around or before point."
   (interactive (browse-url-interactive-arg "W3 URL: "))
   (w3-fetch url))
 
+;; --- Lynx in an xterm ---
+
+;;;###autoload
+(defun browse-url-lynx-xterm (url &optional new-window)
+  ;; new-window ignored
+  "Ask the Lynx WWW browser to load URL.
+Default to the URL around or before point.  A new Lynx process is run
+in an Xterm window."
+  (interactive (browse-url-interactive-arg "Lynx URL: "))
+  (start-process (concat "lynx" url) nil "xterm" "-e" "lynx" url))
+
+(eval-when-compile (require 'term))
+
+;; --- Lynx in an Emacs "term" window ---
+
+;;;###autoload
+(defun browse-url-lynx-emacs (url &optional new-window)
+  ;; new-window ignored
+  "Ask the Lynx WWW browser to load URL.
+Default to the URL around or before point.  Run a new Lynx process in
+an Emacs buffer."
+  (interactive (browse-url-interactive-arg "Lynx URL: "))
+  (let ((system-uses-terminfo t))      ; Lynx uses terminfo
+    (if (fboundp 'make-term)
+       (let ((term-term-name "vt100"))
+         (set-buffer (make-term "browse-url" "lynx" nil url))
+         (term-mode)
+         (term-char-mode)
+         (switch-to-buffer "*browse-url*"))
+      (terminal-emulator "*browse-url*" "lynx" (list url)))))
+
 (provide 'browse-url)
 
 ;;; browse-url.el ends here