123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- """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 is not None:
- alternatives = [using]
- else:
- alternatives = _tryorder
- for browser in alternatives:
- if '%s' in browser:
- # User gave us a command line, don't mess with it.
- return GenericBrowser(browser)
- else:
- # User gave us a browser name.
- try:
- command = _browsers[browser.lower()]
- except KeyError:
- command = _synthesize(browser)
- 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):
- get().open(url, 1)
- def _synthesize(browser):
- """Attempt to synthesize a controller base on existing controllers.
- This is useful to create a controller when a user specifies a path to
- an entry in the BROWSER environment variable -- we can copy a general
- controller to operate using a specific installation of the desired
- browser in this way.
- If we can't create a controller in this way, or if there is no
- executable for the requested browser, return [None, None].
- """
- if not os.path.exists(browser):
- return [None, None]
- name = os.path.basename(browser)
- try:
- command = _browsers[name.lower()]
- except KeyError:
- return [None, None]
- # now attempt to clone to fit the new name:
- controller = command[1]
- if controller and name.lower() == controller.basename:
- import copy
- controller = copy.copy(controller)
- controller.name = browser
- controller.basename = os.path.basename(browser)
- register(browser, None, controller)
- return [None, controller]
- return [None, None]
- def _iscommand(cmd):
- """Return True if cmd can be found on the executable search path."""
- path = os.environ.get("PATH")
- if not path:
- return False
- for d in path.split(os.pathsep):
- exe = os.path.join(d, cmd)
- if os.path.isfile(exe):
- return True
- return False
- PROCESS_CREATION_DELAY = 4
- class GenericBrowser:
- def __init__(self, cmd):
- self.name, self.args = cmd.split(None, 1)
- self.basename = os.path.basename(self.name)
- def open(self, url, new=0, autoraise=1):
- assert "'" not in url
- command = "%s %s" % (self.name, self.args)
- os.system(command % url)
- def open_new(self, url):
- self.open(url)
- class Netscape:
- "Launcher class for Netscape browsers."
- def __init__(self, name):
- self.name = name
- self.basename = os.path.basename(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)
- def open_new(self, url):
- self.open(url, 1)
- class Galeon:
- """Launcher class for Galeon browsers."""
- def __init__(self, name):
- self.name = name
- self.basename = os.path.basename(name)
- def _remote(self, action, autoraise):
- raise_opt = ("--noraise", "")[autoraise]
- cmd = "%s %s %s >/dev/null 2>&1" % (self.name, raise_opt, action)
- rc = os.system(cmd)
- if rc:
- import time
- os.system("%s >/dev/null 2>&1 &" % 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("-w '%s'" % url, autoraise)
- else:
- self._remote("-n '%s'" % url, autoraise)
- def open_new(self, url):
- self.open(url, 1)
- 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 __init__(self):
- if _iscommand("konqueror"):
- self.name = self.basename = "konqueror"
- else:
- self.name = self.basename = "kfm"
- def _remote(self, action):
- cmd = "kfmclient %s >/dev/null 2>&1" % action
- rc = os.system(cmd)
- if rc:
- import time
- if self.basename == "konqueror":
- os.system(self.name + " --silent &")
- else:
- os.system(self.name + " -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.
- assert "'" not in url
- self._remote("openURL '%s'" % url)
- open_new = open
- 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)
- def open_new(self, url):
- self.open(url, 1)
- class WindowsDefault:
- def open(self, url, new=0, autoraise=1):
- os.startfile(url)
- def open_new(self, url):
- self.open(url)
- #
- # 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"):
- _tryorder = ["links", "lynx", "w3m"]
- # 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'"))
- # X browsers have more in the way of options
- if os.environ.get("DISPLAY"):
- _tryorder = ["galeon", "skipstone",
- "mozilla-firefox", "mozilla-firebird", "mozilla", "netscape",
- "kfm", "grail"] + _tryorder
- # First, the Netscape series
- for browser in ("mozilla-firefox", "mozilla-firebird",
- "mozilla", "netscape"):
- if _iscommand(browser):
- register(browser, None, Netscape(browser))
- # Next, Mosaic -- old but still in use.
- if _iscommand("mosaic"):
- register("mosaic", None, GenericBrowser(
- "mosaic '%s' >/dev/null &"))
- # Gnome's Galeon
- if _iscommand("galeon"):
- register("galeon", None, Galeon("galeon"))
- # Skipstone, another Gtk/Mozilla based browser
- if _iscommand("skipstone"):
- register("skipstone", None, GenericBrowser(
- "skipstone '%s' >/dev/null &"))
- # Konqueror/kfm, the KDE browser.
- if _iscommand("kfm") or _iscommand("konqueror"):
- register("kfm", Konqueror, Konqueror())
- # Grail, the Python browser.
- if _iscommand("grail"):
- register("grail", Grail, None)
- class InternetConfig:
- def open(self, url, new=0, autoraise=1):
- ic.launchurl(url)
- def open_new(self, url):
- self.open(url)
- #
- # Platform support for Windows
- #
- if sys.platform[:3] == "win":
- _tryorder = ["netscape", "windows-default"]
- register("windows-default", WindowsDefault)
- #
- # Platform support for MacOS
- #
- try:
- import ic
- except ImportError:
- pass
- else:
- # internet-config is the only supported controller on MacOS,
- # so don't mess with the default!
- _tryorder = ["internet-config"]
- register("internet-config", InternetConfig)
- #
- # Platform support for OS/2
- #
- if sys.platform[:3] == "os2" and _iscommand("netscape.exe"):
- _tryorder = ["os2netscape"]
- register("os2netscape", None,
- GenericBrowser("start netscape.exe %s"))
- # OK, now that we know what the default preference orders for each
- # platform are, allow user to override them with the BROWSER variable.
- #
- if "BROWSER" in os.environ:
- # It's the user's responsibility to register handlers for any unknown
- # browser referenced by this value, before calling open().
- _tryorder[0:0] = os.environ["BROWSER"].split(os.pathsep)
- for cmd in _tryorder:
- if not cmd.lower() in _browsers:
- if _iscommand(cmd.lower()):
- register(cmd.lower(), None, GenericBrowser(
- "%s '%%s'" % cmd.lower()))
- cmd = None # to make del work if _tryorder was empty
- del cmd
- _tryorder = filter(lambda x: x.lower() in _browsers
- or x.find("%s") > -1, _tryorder)
- # what to do if _tryorder is now empty?
|