Rewrite webbrowser.py to fix various bugs following Ka-Ping Yee's
complaints. The new version moves most of its initialization to package load time; it's simpler, faster, smaller, and adds support for Mozilla and Links. Interpretation of the BROWSER variable now works and is documented. The open_new entry point and methods are marked "deprecated; may be removed in 2.1".
This commit is contained in:
parent
f87d857080
commit
f7f185116a
|
@ -15,6 +15,15 @@ browsers will be used if graphical browsers are not available or an X11
|
|||
display isn't available. If text-mode browsers are used, the calling
|
||||
process will block until the user exits the browser.
|
||||
|
||||
Under \UNIX, if the environment variable \envvar{BROWSER} exists, it
|
||||
is interpreted to override the platform default browser, as a
|
||||
colon-separated list of browsers to try in order. When the value of
|
||||
a list part contains the string \code{\%s}, then it is interpreted as
|
||||
a literal browser command line to be used with the argument URL
|
||||
substituted for the \code{\%s}; if the part does not contain,
|
||||
\code{\%s}, it is simply interpreted as the name of the browser to
|
||||
launch.
|
||||
|
||||
For non-\UNIX{} platforms, or when X11 browsers are available on
|
||||
\UNIX, the controlling process will not wait for the user to finish
|
||||
with the browser, but allow the browser to maintain its own window on
|
||||
|
@ -35,11 +44,14 @@ The following functions are defined:
|
|||
|
||||
\begin{funcdesc}{open_new}{url}
|
||||
Open \var{url} in a new window of the default browser, if possible,
|
||||
otherwise, open \var{url} in the only browser window.
|
||||
otherwise, open \var{url} in the only browser window. (This entry
|
||||
point is deprecated and may be removed in 2.1.)
|
||||
\end{funcdesc}
|
||||
|
||||
\begin{funcdesc}{get}{\optional{name}}
|
||||
Return a controller object for the browser type \var{name}.
|
||||
Return a controller object for the browser type \var{name}. If
|
||||
\var{name} is empty, return a controller for a default browser
|
||||
appriopriate
|
||||
\end{funcdesc}
|
||||
|
||||
\begin{funcdesc}{register}{name, constructor\optional{, instance}}
|
||||
|
@ -49,6 +61,10 @@ The following functions are defined:
|
|||
\code{None}, \var{constructor} will be called without parameters to
|
||||
create an instance when needed. If \var{instance} is provided,
|
||||
\var{constructor} will never be called, and may be \code{None}.
|
||||
|
||||
This entry point is only useful if you plan to either set the
|
||||
\envvar{BROWSER} variable or call \function{get} with a nonempty
|
||||
argument matching the name of a handler you declare.
|
||||
\end{funcdesc}
|
||||
|
||||
Several browser types are defined. This table gives the type names
|
||||
|
@ -56,12 +72,16 @@ that may be passed to the \function{get()} function and the names of
|
|||
the implementation classes, all defined in this module.
|
||||
|
||||
\begin{tableiii}{l|l|c}{code}{Type Name}{Class Name}{Notes}
|
||||
\lineiii{'mozilla'}{\class{Mozilla}}{}
|
||||
\lineiii{'netscape'}{\class{Netscape}}{}
|
||||
\lineiii{'mosaic'}{\class{Mosaic}}{}
|
||||
\lineiii{'kfm'}{\class{Konquerer}}{(1)}
|
||||
\lineiii{'grail'}{\class{Grail}}{}
|
||||
\lineiii{'links'}{\class{links}}{}
|
||||
\lineiii{'lynx'}{\class{Lynx}}{}
|
||||
\lineiii{'w3m'}{\class{w3m}}{}
|
||||
\lineiii{'windows-default'}{\class{WindowsDefault}}{(2)}
|
||||
\lineiii{'internet-config'}{\class{InternetConfig}}{(3)}
|
||||
\lineiii{'command-line'}{\class{CommandLineBrowser}}{}
|
||||
\end{tableiii}
|
||||
|
||||
\noindent
|
||||
|
@ -98,5 +118,6 @@ module-level convenience functions:
|
|||
\begin{funcdesc}{open_new}{url}
|
||||
Open \var{url} in a new window of the browser handled by this
|
||||
controller, if possible, otherwise, open \var{url} in the only
|
||||
browser window.
|
||||
browser window. (This method is deprecated and may be removed in
|
||||
2.1.)
|
||||
\end{funcdesc}
|
||||
|
|
|
@ -1,212 +1,239 @@
|
|||
"""Remote-control interfaces to some browsers."""
|
||||
"""Remote-control interfaces to common browsers."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
PROCESS_CREATION_DELAY = 4
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
_browsers = {}
|
||||
_browsers = {} # Dictionary of available browser controllers
|
||||
_tryorder = [] # Preference order of available browsers
|
||||
|
||||
def register(name, klass, instance=None):
|
||||
"""Register a browser connector and, optionally, connection."""
|
||||
_browsers[name.lower()] = [klass, instance]
|
||||
|
||||
|
||||
def get(name=None):
|
||||
"""Retrieve a connection to a browser by type name, or the default
|
||||
browser."""
|
||||
name = name or DEFAULT_BROWSER
|
||||
try:
|
||||
L = _browsers[name.lower()]
|
||||
except KeyError:
|
||||
raise ValueError, "unknown browser type: " + `name`
|
||||
if L[1] is None:
|
||||
L[1] = L[0]()
|
||||
return L[1]
|
||||
|
||||
def get(using=None):
|
||||
"""Return a browser launcher instance appropriate for the environment."""
|
||||
if using:
|
||||
alternatives = [using]
|
||||
else:
|
||||
alternatives = _tryorder
|
||||
for browser in alternatives:
|
||||
if browser.find('%s') > -1:
|
||||
# User gave us a command line, don't mess with it.
|
||||
return browser
|
||||
else:
|
||||
# User gave us a browser name.
|
||||
command = _browsers[browser.lower()]
|
||||
if command[1] is None:
|
||||
return command[0]()
|
||||
else:
|
||||
return command[1]
|
||||
raise Error("could not locate runnable browser")
|
||||
|
||||
# Please note: the following definition hides a builtin function.
|
||||
|
||||
def open(url, new=0):
|
||||
get().open(url, new)
|
||||
|
||||
def open_new(url): # Marked deprecated. May be removed in 2.1.
|
||||
get().open(url, 1)
|
||||
|
||||
def open_new(url):
|
||||
get().open_new(url)
|
||||
#
|
||||
# Everything after this point initializes _browsers and _tryorder,
|
||||
# then disappears. Some class definitions and instances remain
|
||||
# live through these globals, but only the minimum set needed to
|
||||
# support the user's platform.
|
||||
#
|
||||
|
||||
#
|
||||
# Platform support for Unix
|
||||
#
|
||||
|
||||
def _iscommand(cmd):
|
||||
"""Return true if cmd can be found on the executable search path."""
|
||||
path = os.environ.get("PATH")
|
||||
if not path:
|
||||
return 0
|
||||
for d in path.split(os.pathsep):
|
||||
exe = os.path.join(d, cmd)
|
||||
if os.path.isfile(exe):
|
||||
return 1
|
||||
return 0
|
||||
# This is the right test because all these Unix browsers require either
|
||||
# a console terminal of an X display to run. Note that we cannot split
|
||||
# the TERM and DISPLAY cases, because we might be running Python from inside
|
||||
# an xterm.
|
||||
if os.environ.get("TERM") or os.environ.get("DISPLAY"):
|
||||
PROCESS_CREATION_DELAY = 4
|
||||
global tryorder
|
||||
_tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m")
|
||||
|
||||
|
||||
class CommandLineBrowser:
|
||||
_browsers = []
|
||||
if os.environ.get("DISPLAY"):
|
||||
_browsers.extend([
|
||||
("netscape", "netscape %s >/dev/null &"),
|
||||
("mosaic", "mosaic %s >/dev/null &"),
|
||||
])
|
||||
_browsers.extend([
|
||||
("lynx", "lynx %s"),
|
||||
("w3m", "w3m %s"),
|
||||
])
|
||||
|
||||
def open(self, url, new=0):
|
||||
for exe, cmd in self._browsers:
|
||||
if _iscommand(exe):
|
||||
os.system(cmd % url)
|
||||
return
|
||||
raise Error("could not locate runnable browser")
|
||||
|
||||
def open_new(self, url):
|
||||
self.open(url)
|
||||
|
||||
register("command-line", CommandLineBrowser)
|
||||
|
||||
|
||||
class Netscape:
|
||||
autoRaise = 1
|
||||
|
||||
def _remote(self, action):
|
||||
raise_opt = ("-noraise", "-raise")[self.autoRaise]
|
||||
cmd = "netscape %s -remote '%s' >/dev/null 2>&1" % (raise_opt, action)
|
||||
rc = os.system(cmd)
|
||||
if rc:
|
||||
import time
|
||||
os.system("netscape -no-about-splash &")
|
||||
time.sleep(PROCESS_CREATION_DELAY)
|
||||
rc = os.system(cmd)
|
||||
return not rc
|
||||
|
||||
def open(self, url, new=0):
|
||||
if new:
|
||||
self.open_new(url)
|
||||
else:
|
||||
self._remote("openURL(%s)" % url)
|
||||
|
||||
def open_new(self, url):
|
||||
self._remote("openURL(%s, new-window)" % url)
|
||||
|
||||
register("netscape", Netscape)
|
||||
|
||||
|
||||
class Konqueror:
|
||||
"""Controller for the KDE File Manager (kfm, or Konqueror).
|
||||
|
||||
See http://developer.kde.org/documentation/other/kfmclient.html
|
||||
for more information on the Konqueror remote-control interface.
|
||||
|
||||
"""
|
||||
def _remote(self, action):
|
||||
cmd = "kfmclient %s >/dev/null 2>&1" % action
|
||||
rc = os.system(cmd)
|
||||
if rc:
|
||||
import time
|
||||
os.system("kfm -d &")
|
||||
time.sleep(PROCESS_CREATION_DELAY)
|
||||
rc = os.system(cmd)
|
||||
return not rc
|
||||
|
||||
def open(self, url, new=1):
|
||||
# XXX currently I know no way to prevent KFM from opening a new win.
|
||||
self.open_new(url)
|
||||
|
||||
def open_new(self, url):
|
||||
self._remote("openURL %s" % url)
|
||||
|
||||
register("kfm", Konqueror)
|
||||
|
||||
|
||||
class Grail:
|
||||
# There should be a way to maintain a connection to Grail, but the
|
||||
# Grail remote control protocol doesn't really allow that at this
|
||||
# point. It probably never will!
|
||||
|
||||
def _find_grail_rc(self):
|
||||
import glob
|
||||
import pwd
|
||||
import socket
|
||||
import tempfile
|
||||
tempdir = os.path.join(tempfile.gettempdir(), ".grail-unix")
|
||||
user = pwd.getpwuid(_os.getuid())[0]
|
||||
filename = os.path.join(tempdir, user + "-*")
|
||||
maybes = glob.glob(filename)
|
||||
if not maybes:
|
||||
return None
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
for fn in maybes:
|
||||
# need to PING each one until we find one that's live
|
||||
try:
|
||||
s.connect(fn)
|
||||
except socket.error:
|
||||
# no good; attempt to clean it out, but don't fail:
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
return s
|
||||
|
||||
def _remote(self, action):
|
||||
s = self._find_grail_rc()
|
||||
if not s:
|
||||
def _iscommand(cmd):
|
||||
"""Return true if cmd can be found on the executable search path."""
|
||||
path = os.environ.get("PATH")
|
||||
if not path:
|
||||
return 0
|
||||
s.send(action)
|
||||
s.close()
|
||||
return 1
|
||||
for d in path.split(os.pathsep):
|
||||
exe = os.path.join(d, cmd)
|
||||
if os.path.isfile(exe):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def open(self, url, new=0):
|
||||
if new:
|
||||
self.open_new(url)
|
||||
else:
|
||||
self._remote("LOAD " + url)
|
||||
class GenericBrowser:
|
||||
def __init__(self, cmd):
|
||||
self.command = cmd
|
||||
|
||||
def open_new(self, url):
|
||||
self._remote("LOADNEW " + url)
|
||||
def open(self, url, new=0):
|
||||
os.system(self.command % url)
|
||||
|
||||
register("grail", Grail)
|
||||
def open_new(self, url): # Deprecated. May be removed in 2.1.
|
||||
self.open(url)
|
||||
|
||||
# Easy cases first -- register console browsers if we have them.
|
||||
if os.environ.get("TERM"):
|
||||
# The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
|
||||
if _iscommand("links"):
|
||||
register("links", None, GenericBrowser("links %s"))
|
||||
# The Lynx browser <http://lynx.browser.org/>
|
||||
if _iscommand("lynx"):
|
||||
register("lynx", None, GenericBrowser("lynx %s"))
|
||||
# The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
|
||||
if _iscommand("w3m"):
|
||||
register("w3m", None, GenericBrowser("w3m %s"))
|
||||
|
||||
class WindowsDefault:
|
||||
def open(self, url, new=0):
|
||||
os.startfile(url)
|
||||
# X browsers have mre in the way of options
|
||||
if os.environ.get("DISPLAY"):
|
||||
# First, the Netscape series
|
||||
if _iscommand("netscape") or _iscommand("mozilla"):
|
||||
class Netscape:
|
||||
"Launcher class for Netscape browsers."
|
||||
autoRaise = 1
|
||||
|
||||
def open_new(self, url):
|
||||
self.open(url)
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def _remote(self, action):
|
||||
raise_opt = ("-noraise", "-raise")[self.autoRaise]
|
||||
cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name, raise_opt, action)
|
||||
rc = os.system(cmd)
|
||||
if rc:
|
||||
import time
|
||||
os.system("%s -no-about-splash &" % self.name)
|
||||
time.sleep(PROCESS_CREATION_DELAY)
|
||||
rc = os.system(cmd)
|
||||
return not rc
|
||||
|
||||
DEFAULT_BROWSER = "command-line"
|
||||
def open(self, url, new=0):
|
||||
if new:
|
||||
self._remote("openURL(%s, new-window)" % url)
|
||||
else:
|
||||
self._remote("openURL(%s)" % url)
|
||||
|
||||
# Deprecated. May be removed in 2.1.
|
||||
def open_new(self, url):
|
||||
self.open(url, 1)
|
||||
|
||||
if _iscommand("mozilla"):
|
||||
register("mozilla", None, Netscape("mozilla"))
|
||||
if _iscommand("netscape"):
|
||||
register("netscape", None, Netscape("netscape"))
|
||||
|
||||
# Next, Mosaic -- old but still in use.
|
||||
if _iscommand("mosaic"):
|
||||
register("mosaic", None, GenericBrowser("mosaic %s >/dev/null &"))
|
||||
|
||||
# Konqueror/kfm, the KDE browser.
|
||||
if _iscommand("kfm"):
|
||||
class Konqueror:
|
||||
"""Controller for the KDE File Manager (kfm, or Konqueror).
|
||||
|
||||
See http://developer.kde.org/documentation/other/kfmclient.html
|
||||
for more information on the Konqueror remote-control interface.
|
||||
|
||||
"""
|
||||
def _remote(self, action):
|
||||
cmd = "kfmclient %s >/dev/null 2>&1" % action
|
||||
rc = os.system(cmd)
|
||||
if rc:
|
||||
import time
|
||||
os.system("kfm -d &")
|
||||
time.sleep(PROCESS_CREATION_DELAY)
|
||||
rc = os.system(cmd)
|
||||
return not rc
|
||||
|
||||
def open(self, url, new=1):
|
||||
# XXX Currently I know no way to prevent KFM from opening a new win.
|
||||
self._remote("openURL %s" % url)
|
||||
|
||||
# Deprecated. May be removed in 2.1.
|
||||
open_new = open
|
||||
|
||||
|
||||
register("kfm", Konqueror, None)
|
||||
|
||||
# Grail, the Python browser.
|
||||
if _iscommand("grail"):
|
||||
class Grail:
|
||||
# There should be a way to maintain a connection to
|
||||
# Grail, but the Grail remote control protocol doesn't
|
||||
# really allow that at this point. It probably neverwill!
|
||||
def _find_grail_rc(self):
|
||||
import glob
|
||||
import pwd
|
||||
import socket
|
||||
import tempfile
|
||||
tempdir = os.path.join(tempfile.gettempdir(), ".grail-unix")
|
||||
user = pwd.getpwuid(_os.getuid())[0]
|
||||
filename = os.path.join(tempdir, user + "-*")
|
||||
maybes = glob.glob(filename)
|
||||
if not maybes:
|
||||
return None
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
for fn in maybes:
|
||||
# need to PING each one until we find one that's live
|
||||
try:
|
||||
s.connect(fn)
|
||||
except socket.error:
|
||||
# no good; attempt to clean it out, but don't fail:
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
return s
|
||||
|
||||
def _remote(self, action):
|
||||
s = self._find_grail_rc()
|
||||
if not s:
|
||||
return 0
|
||||
s.send(action)
|
||||
s.close()
|
||||
return 1
|
||||
|
||||
def open(self, url, new=0):
|
||||
if new:
|
||||
self._remote("LOADNEW " + url)
|
||||
else:
|
||||
self._remote("LOAD " + url)
|
||||
|
||||
# Deprecated. May be removed in 2.1.
|
||||
def open_new(self, url):
|
||||
self.open(url, 1)
|
||||
|
||||
register("grail", Grail, None)
|
||||
|
||||
#
|
||||
# Platform support for Windows
|
||||
#
|
||||
|
||||
if sys.platform[:3] == "win":
|
||||
del _browsers["kfm"]
|
||||
global _tryorder
|
||||
_tryorder = ("netscape", "windows-default")
|
||||
|
||||
class WindowsDefault:
|
||||
def open(self, url, new=0):
|
||||
os.startfile(url)
|
||||
|
||||
def open_new(self, url): # Deprecated. May be removed in 2.1.
|
||||
self.open(url)
|
||||
|
||||
register("windows-default", WindowsDefault)
|
||||
DEFAULT_BROWSER = "windows-default"
|
||||
elif os.environ.get("DISPLAY"):
|
||||
if _iscommand("netscape"):
|
||||
DEFAULT_BROWSER = "netscape"
|
||||
|
||||
# If the $BROWSER environment variable is set and true, let that be
|
||||
# the name of the browser to use:
|
||||
#
|
||||
DEFAULT_BROWSER = os.environ.get("BROWSER") or DEFAULT_BROWSER
|
||||
|
||||
|
||||
# Now try to support the MacOS world. This is the only supported
|
||||
# controller on that platform, so don't mess with the default!
|
||||
# Platform support for MacOS
|
||||
#
|
||||
|
||||
try:
|
||||
import ic
|
||||
|
@ -217,9 +244,26 @@ else:
|
|||
def open(self, url, new=0):
|
||||
ic.launchurl(url)
|
||||
|
||||
def open_new(self, url):
|
||||
def open_new(self, url): # Deprecated. May be removed in 2.1.
|
||||
self.open(url)
|
||||
|
||||
_browsers.clear()
|
||||
# internet-config is the only supported controller on MacOS,
|
||||
# so don't mess with the default!
|
||||
_tryorder = ("internet-config")
|
||||
register("internet-config", InternetConfig)
|
||||
DEFAULT_BROWSER = "internet-config"
|
||||
|
||||
# OK, now that we know what the default preference orders for each
|
||||
# platform are, allow user to override them with the BROWSER variable.
|
||||
#
|
||||
if os.environ.has_key("BROWSER"):
|
||||
# It's the user's responsibility to register handlers for any unknown
|
||||
# browser referenced by this value, before calling open().
|
||||
_tryorder = os.environ["BROWSER"].split(":")
|
||||
else:
|
||||
# Optimization: filter out alternatives that aren't available, so we can
|
||||
# avoid has_key() tests at runtime. (This may also allow some unused
|
||||
# classes and class-instance storage to be garbage-collected.)
|
||||
_tryorder = filter(lambda x: _browsers.has_key(x.lower()) or x.find("%s")>-1,\
|
||||
_tryorder)
|
||||
|
||||
# end
|
||||
|
|
Loading…
Reference in New Issue