diff options
Diffstat (limited to 'Lib/webbrowser.py')
-rw-r--r-- | Lib/webbrowser.py | 513 |
1 files changed, 338 insertions, 175 deletions
diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index 4750fe2..6c34f8b 100644 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -1,9 +1,11 @@ +#! /usr/bin/env python """Interfaces for launching and remotely controlling Web browsers.""" import os import sys +import stat -__all__ = ["Error", "open", "get", "register"] +__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] class Error(Exception): pass @@ -11,9 +13,13 @@ class Error(Exception): _browsers = {} # Dictionary of available browser controllers _tryorder = [] # Preference order of available browsers -def register(name, klass, instance=None): +def register(name, klass, instance=None, update_tryorder=1): """Register a browser connector and, optionally, connection.""" _browsers[name.lower()] = [klass, instance] + if update_tryorder > 0: + _tryorder.append(name) + elif update_tryorder < 0: + _tryorder.insert(0, name) def get(using=None): """Return a browser launcher instance appropriate for the environment.""" @@ -26,27 +32,36 @@ def get(using=None): # User gave us a command line, don't mess with it. return GenericBrowser(browser) else: - # User gave us a browser name. + # User gave us a browser name or path. try: command = _browsers[browser.lower()] except KeyError: command = _synthesize(browser) - if command[1] is None: - return command[0]() - else: + if command[1] is not None: return command[1] + elif command[0] is not None: + return command[0]() raise Error("could not locate runnable browser") # Please note: the following definition hides a builtin function. +# It is recommended one does "import webbrowser" and uses webbrowser.open(url) +# instead of "from webbrowser import *". def open(url, new=0, autoraise=1): - get().open(url, new, autoraise) + for name in _tryorder: + browser = get(name) + if browser.open(url, new, autoraise): + return True + return False def open_new(url): - get().open(url, 1) + return open(url, 1) +def open_new_tab(url): + return open(url, 2) -def _synthesize(browser): + +def _synthesize(browser, update_tryorder=1): """Attempt to synthesize a controller base on existing controllers. This is useful to create a controller when a user specifies a path to @@ -58,9 +73,10 @@ def _synthesize(browser): executable for the requested browser, return [None, None]. """ - if not os.path.exists(browser): + cmd = browser.split()[0] + if not _iscommand(cmd): return [None, None] - name = os.path.basename(browser) + name = os.path.basename(cmd) try: command = _browsers[name.lower()] except KeyError: @@ -72,132 +88,199 @@ def _synthesize(browser): controller = copy.copy(controller) controller.name = browser controller.basename = os.path.basename(browser) - register(browser, None, controller) + register(browser, None, controller, update_tryorder) return [None, controller] return [None, None] +if sys.platform[:3] == "win": + def _isexecutable(cmd): + cmd = cmd.lower() + if os.path.isfile(cmd) and (cmd.endswith(".exe") or + cmd.endswith(".bat")): + return True + for ext in ".exe", ".bat": + if os.path.isfile(cmd + ext): + return True + return False +else: + def _isexecutable(cmd): + if os.path.isfile(cmd): + mode = os.stat(cmd)[stat.ST_MODE] + if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH: + return True + return False + def _iscommand(cmd): - """Return True if cmd can be found on the executable search path.""" + """Return True if cmd is executable or can be found on the executable + search path.""" + if _isexecutable(cmd): + return True 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): + if _isexecutable(exe): return True return False -PROCESS_CREATION_DELAY = 4 +# General parent classes + +class BaseBrowser(object): + """Parent class for all browsers.""" + + def __init__(self, name=""): + self.name = name + + def open_new(self, url): + return self.open(url, 1) + + def open_new_tab(self, url): + return self.open(url, 2) + +class GenericBrowser(BaseBrowser): + """Class for all browsers started with a command + and without remote functionality.""" -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) + rc = os.system(command % url) + return not rc - def open_new(self, url): - self.open(url) +class UnixBrowser(BaseBrowser): + """Parent class for all Unix browsers with remote functionality.""" -class Netscape: - "Launcher class for Netscape browsers." - def __init__(self, name): - self.name = name - self.basename = os.path.basename(name) + raise_opts = None - def _remote(self, action, autoraise): - raise_opt = ("-noraise", "-raise")[autoraise] - cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name, - raise_opt, - action) + remote_cmd = '' + remote_action = None + remote_action_newwin = None + remote_action_newtab = None + remote_background = False + + def _remote(self, url, action, autoraise): + autoraise = int(bool(autoraise)) # always 0/1 + raise_opt = self.raise_opts and self.raise_opts[autoraise] or '' + cmd = "%s %s %s '%s' >/dev/null 2>&1" % (self.name, raise_opt, + self.remote_cmd, action) + if remote_background: + cmd += ' &' rc = os.system(cmd) if rc: - import time - os.system("%s &" % self.name) - time.sleep(PROCESS_CREATION_DELAY) - rc = os.system(cmd) + # bad return status, try again with simpler command + rc = os.system("%s %s" % (self.name, url)) return not rc def open(self, url, new=0, autoraise=1): - if new: - self._remote("openURL(%s, new-window)"%url, autoraise) + assert "'" not in url + if new == 0: + action = self.remote_action + elif new == 1: + action = self.remote_action_newwin + elif new == 2: + if self.remote_action_newtab is None: + action = self.remote_action_newwin + else: + action = self.remote_action_newtab else: - self._remote("openURL(%s)" % url, autoraise) + raise Error("Bad 'new' parameter to open(); expected 0, 1, or 2, got %s" % new) + return self._remote(url, action % url, autoraise) - def open_new(self, url): - self.open(url, 1) +class Mozilla(UnixBrowser): + """Launcher class for Mozilla/Netscape browsers.""" -class Galeon: - """Launcher class for Galeon browsers.""" - def __init__(self, name): - self.name = name - self.basename = os.path.basename(name) + raise_opts = ("-noraise", "-raise") - 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 + remote_cmd = '-remote' + remote_action = "openURL(%s)" + remote_action_newwin = "openURL(%s,new-window)" + remote_action_newtab = "openURL(%s,new-tab)" - def open(self, url, new=0, autoraise=1): - if new: - self._remote("-w '%s'" % url, autoraise) - else: - self._remote("-n '%s'" % url, autoraise) +Netscape = Mozilla - def open_new(self, url): - self.open(url, 1) +class Galeon(UnixBrowser): + """Launcher class for Galeon/Epiphany browsers.""" + + raise_opts = ("-noraise", "") + remote_action = "-n '%s'" + remote_action_newwin = "-w '%s'" -class Konqueror: + remote_background = True + + +class Konqueror(BaseBrowser): """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): + def _remote(self, url, action): + # kfmclient is the new KDE way of opening URLs. cmd = "kfmclient %s >/dev/null 2>&1" % action rc = os.system(cmd) + # Fall back to other variants. 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) + if _iscommand("konqueror"): + rc = os.system(self.name + " --silent '%s' &" % url) + elif _iscommand("kfm"): + rc = os.system(self.name + " -d '%s'" % url) return not rc - def open(self, url, new=1, autoraise=1): + def open(self, url, new=0, 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) + if new == 2: + action = "newTab '%s'" % url + else: + action = "openURL '%s'" % url + ok = self._remote(url, action) + return ok + + +class Opera(UnixBrowser): + "Launcher class for Opera browser." - open_new = open + raise_opts = ("", "-raise") + remote_cmd = '-remote' + remote_action = "openURL(%s)" + remote_action_newwin = "openURL(%s,new-window)" + remote_action_newtab = "openURL(%s,new-page)" -class Grail: + +class Elinks(UnixBrowser): + "Launcher class for Elinks browsers." + + remote_cmd = '-remote' + remote_action = "openURL(%s)" + remote_action_newwin = "openURL(%s,new-window)" + remote_action_newtab = "openURL(%s,new-tab)" + + def _remote(self, url, action, autoraise): + # elinks doesn't like its stdout to be redirected - + # it uses redirected stdout as a signal to do -dump + cmd = "%s %s '%s' 2>/dev/null" % (self.name, + self.remote_cmd, action) + rc = os.system(cmd) + if rc: + rc = os.system("%s %s" % (self.name, url)) + return not rc + + +class Grail(BaseBrowser): # 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! @@ -237,93 +320,97 @@ class Grail: def open(self, url, new=0, autoraise=1): if new: - self._remote("LOADNEW " + url) + ok = self._remote("LOADNEW " + url) else: - self._remote("LOAD " + url) + ok = self._remote("LOAD " + url) + return ok - 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) - +# These are the right tests because all these Unix browsers require either +# a console terminal or an X display to run. + +# Prefer X browsers if present +if os.environ.get("DISPLAY"): + + # First, the Mozilla/Netscape browsers + for browser in ("mozilla-firefox", "firefox", + "mozilla-firebird", "firebird", + "mozilla", "netscape"): + if _iscommand(browser): + register(browser, None, Mozilla(browser)) + + # The default Gnome browser + if _iscommand("gconftool-2"): + # get the web browser string from gconftool + gc = 'gconftool-2 -g /desktop/gnome/url-handlers/http/command' + out = os.popen(gc) + commd = out.read().strip() + retncode = out.close() + + # if successful, register it + if retncode == None and len(commd) != 0: + register("gnome", None, GenericBrowser( + commd + " '%s' >/dev/null &")) + + # Konqueror/kfm, the KDE browser. + if _iscommand("kfm") or _iscommand("konqueror"): + register("kfm", Konqueror, Konqueror()) + + # Gnome's Galeon and Epiphany + for browser in ("galeon", "epiphany"): + if _iscommand(browser): + register(browser, None, Galeon(browser)) + + # Skipstone, another Gtk/Mozilla based browser + if _iscommand("skipstone"): + register("skipstone", None, GenericBrowser("skipstone '%s' &")) + + # Opera, quite popular + if _iscommand("opera"): + register("opera", None, Opera("opera")) + + # Next, Mosaic -- old but still in use. + if _iscommand("mosaic"): + register("mosaic", None, GenericBrowser("mosaic '%s' &")) + + # Grail, the Python browser. Does anybody still use it? + if _iscommand("grail"): + register("grail", Grail, None) + +# Also try console browsers +if os.environ.get("TERM"): + # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/> + if _iscommand("links"): + register("links", None, GenericBrowser("links '%s'")) + if _iscommand("elinks"): + register("elinks", None, Elinks("elinks")) + # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/> + if _iscommand("lynx"): + register("lynx", None, GenericBrowser("lynx '%s'")) + # The w3m browser <http://w3m.sourceforge.net/> + if _iscommand("w3m"): + register("w3m", None, GenericBrowser("w3m '%s'")) # # Platform support for Windows # if sys.platform[:3] == "win": - _tryorder = ["netscape", "windows-default"] + class WindowsDefault(BaseBrowser): + def open(self, url, new=0, autoraise=1): + os.startfile(url) + return True # Oh, my... + + _tryorder = [] + _browsers = {} + # Prefer mozilla/netscape/opera if present + for browser in ("firefox", "firebird", "mozilla", "netscape", "opera"): + if _iscommand(browser): + register(browser, None, GenericBrowser(browser + ' %s')) register("windows-default", WindowsDefault) # @@ -335,36 +422,112 @@ try: 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) + class InternetConfig(BaseBrowser): + def open(self, url, new=0, autoraise=1): + ic.launchurl(url) + return True # Any way to get status? + + register("internet-config", InternetConfig, update_tryorder=-1) + +if sys.platform == 'darwin': + # Adapted from patch submitted to SourceForge by Steven J. Burr + class MacOSX(BaseBrowser): + """Launcher class for Aqua browsers on Mac OS X + + Optionally specify a browser name on instantiation. Note that this + will not work for Aqua browsers if the user has moved the application + package after installation. + + If no browser is specified, the default browser, as specified in the + Internet System Preferences panel, will be used. + """ + def __init__(self, name): + self.name = name + + def open(self, url, new=0, autoraise=1): + assert "'" not in url + # new must be 0 or 1 + new = int(bool(new)) + if self.name == "default": + # User called open, open_new or get without a browser parameter + script = _safequote('open location "%s"', url) # opens in default browser + else: + # User called get and chose a browser + if self.name == "OmniWeb": + toWindow = "" + else: + # Include toWindow parameter of OpenURL command for browsers + # that support it. 0 == new window; -1 == existing + toWindow = "toWindow %d" % (new - 1) + cmd = _safequote('OpenURL "%s"', url) + script = '''tell application "%s" + activate + %s %s + end tell''' % (self.name, cmd, toWindow) + # Open pipe to AppleScript through osascript command + osapipe = os.popen("osascript", "w") + if osapipe is None: + return False + # Write script to osascript's stdin + osapipe.write(script) + rc = osapipe.close() + return not rc + + # Don't clear _tryorder or _browsers since OS X can use above Unix support + # (but we prefer using the OS X specific stuff) + register("MacOSX", None, MacOSX('default'), -1) + # # Platform support for OS/2 # -if sys.platform[:3] == "os2" and _iscommand("netscape.exe"): - _tryorder = ["os2netscape"] +if sys.platform[:3] == "os2" and _iscommand("netscape"): + _tryorder = [] + _browsers = {} register("os2netscape", None, - GenericBrowser("start netscape.exe %s")) + GenericBrowser("start netscape %s"), -1) + # 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 = 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) + _userchoices = os.environ["BROWSER"].split(os.pathsep) + _userchoices.reverse() + + # Treat choices in same way as if passed into get() but do register + # and prepend to _tryorder + for cmdline in _userchoices: + if cmdline != '': + _synthesize(cmdline, -1) + cmdline = None # to make del work if _userchoices was empty + del cmdline + del _userchoices + # what to do if _tryorder is now empty? + + +def main(): + import getopt + usage = """Usage: %s [-n | -t] url + -n: open new window + -t: open new tab""" % sys.argv[0] + try: + opts, args = getopt.getopt(sys.argv[1:], 'ntd') + except getopt.error, msg: + print >>sys.stderr, msg + print >>sys.stderr, usage + sys.exit(1) + new_win = 0 + for o, a in opts: + if o == '-n': new_win = 1 + elif o == '-t': new_win = 2 + if len(args) <> 1: + print >>sys.stderr, usage + sys.exit(1) + + url = args[0] + open(url, new_win) + +if __name__ == "__main__": + main() |