webbrowser.py revision f7eb4faf38fc9e4a215acae3323140b99cdce08f
1"""Interfaces for launching and remotely controlling Web browsers.""" 2 3import os 4import sys 5 6__all__ = ["Error", "open", "get", "register"] 7 8class Error(Exception): 9 pass 10 11_browsers = {} # Dictionary of available browser controllers 12_tryorder = [] # Preference order of available browsers 13 14def register(name, klass, instance=None): 15 """Register a browser connector and, optionally, connection.""" 16 _browsers[name.lower()] = [klass, instance] 17 18def get(using=None): 19 """Return a browser launcher instance appropriate for the environment.""" 20 if using: 21 alternatives = [using] 22 else: 23 alternatives = _tryorder 24 for browser in alternatives: 25 if browser.find('%s') > -1: 26 # User gave us a command line, don't mess with it. 27 return GenericBrowser(browser) 28 else: 29 # User gave us a browser name. 30 command = _browsers[browser.lower()] 31 if command[1] is None: 32 return command[0]() 33 else: 34 return command[1] 35 raise Error("could not locate runnable browser") 36 37# Please note: the following definition hides a builtin function. 38 39def open(url, new=0, autoraise=1): 40 get().open(url, new, autoraise) 41 42def open_new(url): # Marked deprecated. May be removed in 2.1. 43 get().open(url, 1) 44 45# 46# Everything after this point initializes _browsers and _tryorder, 47# then disappears. Some class definitions and instances remain 48# live through these globals, but only the minimum set needed to 49# support the user's platform. 50# 51 52# 53# Platform support for Unix 54# 55 56# This is the right test because all these Unix browsers require either 57# a console terminal of an X display to run. Note that we cannot split 58# the TERM and DISPLAY cases, because we might be running Python from inside 59# an xterm. 60if os.environ.get("TERM") or os.environ.get("DISPLAY"): 61 PROCESS_CREATION_DELAY = 4 62 _tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m") 63 64 def _iscommand(cmd): 65 """Return true if cmd can be found on the executable search path.""" 66 path = os.environ.get("PATH") 67 if not path: 68 return 0 69 for d in path.split(os.pathsep): 70 exe = os.path.join(d, cmd) 71 if os.path.isfile(exe): 72 return 1 73 return 0 74 75 class GenericBrowser: 76 def __init__(self, cmd): 77 self.command = cmd 78 79 def open(self, url, new=0, autoraise=1): 80 os.system(self.command % url) 81 82 def open_new(self, url): # Deprecated. May be removed in 2.1. 83 self.open(url) 84 85 # Easy cases first -- register console browsers if we have them. 86 if os.environ.get("TERM"): 87 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/> 88 if _iscommand("links"): 89 register("links", None, GenericBrowser("links %s")) 90 # The Lynx browser <http://lynx.browser.org/> 91 if _iscommand("lynx"): 92 register("lynx", None, GenericBrowser("lynx %s")) 93 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/> 94 if _iscommand("w3m"): 95 register("w3m", None, GenericBrowser("w3m %s")) 96 97 # X browsers have more in the way of options 98 if os.environ.get("DISPLAY"): 99 # First, the Netscape series 100 if _iscommand("netscape") or _iscommand("mozilla"): 101 class Netscape: 102 "Launcher class for Netscape browsers." 103 def __init__(self, name): 104 self.name = name 105 106 def _remote(self, action, autoraise): 107 raise_opt = ("-noraise", "-raise")[autoraise] 108 cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name, 109 raise_opt, 110 action) 111 rc = os.system(cmd) 112 if rc: 113 import time 114 os.system("%s &" % self.name) 115 time.sleep(PROCESS_CREATION_DELAY) 116 rc = os.system(cmd) 117 return not rc 118 119 def open(self, url, new=0, autoraise=1): 120 if new: 121 self._remote("openURL(%s, new-window)"%url, autoraise) 122 else: 123 self._remote("openURL(%s)" % url, autoraise) 124 125 # Deprecated. May be removed in 2.1. 126 def open_new(self, url): 127 self.open(url, 1) 128 129 if _iscommand("mozilla"): 130 register("mozilla", None, Netscape("mozilla")) 131 if _iscommand("netscape"): 132 register("netscape", None, Netscape("netscape")) 133 134 # Next, Mosaic -- old but still in use. 135 if _iscommand("mosaic"): 136 register("mosaic", None, GenericBrowser("mosaic %s >/dev/null &")) 137 138 # Konqueror/kfm, the KDE browser. 139 if _iscommand("kfm") or _iscommand("konqueror"): 140 class Konqueror: 141 """Controller for the KDE File Manager (kfm, or Konqueror). 142 143 See http://developer.kde.org/documentation/other/kfmclient.html 144 for more information on the Konqueror remote-control interface. 145 146 """ 147 def _remote(self, action): 148 cmd = "kfmclient %s >/dev/null 2>&1" % action 149 rc = os.system(cmd) 150 if rc: 151 import time 152 if _iscommand("konqueror"): 153 os.system("konqueror --silent &") 154 else: 155 os.system("kfm -d &") 156 time.sleep(PROCESS_CREATION_DELAY) 157 rc = os.system(cmd) 158 return not rc 159 160 def open(self, url, new=1, autoraise=1): 161 # XXX Currently I know no way to prevent KFM from 162 # opening a new win. 163 self._remote("openURL %s" % url) 164 165 # Deprecated. May be removed in 2.1. 166 open_new = open 167 168 register("kfm", Konqueror, None) 169 170 # Grail, the Python browser. 171 if _iscommand("grail"): 172 class Grail: 173 # There should be a way to maintain a connection to 174 # Grail, but the Grail remote control protocol doesn't 175 # really allow that at this point. It probably neverwill! 176 def _find_grail_rc(self): 177 import glob 178 import pwd 179 import socket 180 import tempfile 181 tempdir = os.path.join(tempfile.gettempdir(), 182 ".grail-unix") 183 user = pwd.getpwuid(_os.getuid())[0] 184 filename = os.path.join(tempdir, user + "-*") 185 maybes = glob.glob(filename) 186 if not maybes: 187 return None 188 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 189 for fn in maybes: 190 # need to PING each one until we find one that's live 191 try: 192 s.connect(fn) 193 except socket.error: 194 # no good; attempt to clean it out, but don't fail: 195 try: 196 os.unlink(fn) 197 except IOError: 198 pass 199 else: 200 return s 201 202 def _remote(self, action): 203 s = self._find_grail_rc() 204 if not s: 205 return 0 206 s.send(action) 207 s.close() 208 return 1 209 210 def open(self, url, new=0, autoraise=1): 211 if new: 212 self._remote("LOADNEW " + url) 213 else: 214 self._remote("LOAD " + url) 215 216 # Deprecated. May be removed in 2.1. 217 def open_new(self, url): 218 self.open(url, 1) 219 220 register("grail", Grail, None) 221 222# 223# Platform support for Windows 224# 225 226if sys.platform[:3] == "win": 227 _tryorder = ("netscape", "windows-default") 228 229 class WindowsDefault: 230 def open(self, url, new=0, autoraise=1): 231 os.startfile(url) 232 233 def open_new(self, url): # Deprecated. May be removed in 2.1. 234 self.open(url) 235 236 register("windows-default", WindowsDefault) 237 238# 239# Platform support for MacOS 240# 241 242try: 243 import ic 244except ImportError: 245 pass 246else: 247 class InternetConfig: 248 def open(self, url, new=0, autoraise=1): 249 ic.launchurl(url) 250 251 def open_new(self, url): # Deprecated. May be removed in 2.1. 252 self.open(url) 253 254 # internet-config is the only supported controller on MacOS, 255 # so don't mess with the default! 256 _tryorder = ("internet-config") 257 register("internet-config", InternetConfig) 258 259# OK, now that we know what the default preference orders for each 260# platform are, allow user to override them with the BROWSER variable. 261# 262if os.environ.has_key("BROWSER"): 263 # It's the user's responsibility to register handlers for any unknown 264 # browser referenced by this value, before calling open(). 265 _tryorder = os.environ["BROWSER"].split(":") 266else: 267 # Optimization: filter out alternatives that aren't available, so we can 268 # avoid has_key() tests at runtime. (This may also allow some unused 269 # classes and class-instance storage to be garbage-collected.) 270 _tryorder = filter(lambda x: _browsers.has_key(x.lower()) 271 or x.find("%s") > -1, _tryorder) 272 273# end 274