"""Interfaces for launching and remotely controlling Web browsers.""" import os import sys __all__ = ["Error", "open", "get", "register"] class Error(Exception): pass _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(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, autoraise=1): get().open(url, new, autoraise) def open_new(url): # Marked deprecated. May be removed in 2.1. get().open(url, 1) # # 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 # # 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 _tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m") 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 class GenericBrowser: def __init__(self, cmd): self.command = cmd def open(self, url, new=0, autoraise=1): os.system(self.command % url) 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 if _iscommand("links"): register("links", None, GenericBrowser("links %s")) # The Lynx browser if _iscommand("lynx"): register("lynx", None, GenericBrowser("lynx %s")) # The w3m browser if _iscommand("w3m"): register("w3m", None, GenericBrowser("w3m %s")) # X browsers have more 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." def __init__(self, name): self.name = name def _remote(self, action, autoraise): raise_opt = ("-noraise", "-raise")[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 &" % self.name) time.sleep(PROCESS_CREATION_DELAY) rc = os.system(cmd) return not rc def open(self, url, new=0, autoraise=1): if new: self._remote("openURL(%s, new-window)"%url, autoraise) else: self._remote("openURL(%s)" % url, autoraise) # 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, autoraise=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, autoraise=1): 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": _tryorder = ("netscape", "windows-default") class WindowsDefault: def open(self, url, new=0, autoraise=1): os.startfile(url) def open_new(self, url): # Deprecated. May be removed in 2.1. self.open(url) register("windows-default", WindowsDefault) # # Platform support for MacOS # try: import ic except ImportError: pass else: class InternetConfig: def open(self, url, new=0, autoraise=1): ic.launchurl(url) def open_new(self, url): # Deprecated. May be removed in 2.1. self.open(url) # internet-config is the only supported controller on MacOS, # so don't mess with the default! _tryorder = ("internet-config") register("internet-config", InternetConfig) # 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