0001 """Interfaces for launching and remotely controlling Web browsers.""" 0002 0003 import os 0004 import sys 0005 0006 __all__ = ["Error", "open", "get", "register"] 0007 0008 class Error(Exception): 0009 pass 0010 0011 _browsers = {} # Dictionary of available browser controllers 0012 _tryorder = [] # Preference order of available browsers 0013 0014 def register(name, klass, instance=None): 0015 """Register a browser connector and, optionally, connection.""" 0016 _browsers[name.lower()] = [klass, instance] 0017 0018 def get(using=None): 0019 """Return a browser launcher instance appropriate for the environment.""" 0020 if using is not None: 0021 alternatives = [using] 0022 else: 0023 alternatives = _tryorder 0024 for browser in alternatives: 0025 if '%s' in browser: 0026 # User gave us a command line, don't mess with it. 0027 return GenericBrowser(browser) 0028 else: 0029 # User gave us a browser name. 0030 try: 0031 command = _browsers[browser.lower()] 0032 except KeyError: 0033 command = _synthesize(browser) 0034 if command[1] is None: 0035 return command[0]() 0036 else: 0037 return command[1] 0038 raise Error("could not locate runnable browser") 0039 0040 # Please note: the following definition hides a builtin function. 0041 0042 def open(url, new=0, autoraise=1): 0043 get().open(url, new, autoraise) 0044 0045 def open_new(url): 0046 get().open(url, 1) 0047 0048 0049 def _synthesize(browser): 0050 """Attempt to synthesize a controller base on existing controllers. 0051 0052 This is useful to create a controller when a user specifies a path to 0053 an entry in the BROWSER environment variable -- we can copy a general 0054 controller to operate using a specific installation of the desired 0055 browser in this way. 0056 0057 If we can't create a controller in this way, or if there is no 0058 executable for the requested browser, return [None, None]. 0059 0060 """ 0061 if not os.path.exists(browser): 0062 return [None, None] 0063 name = os.path.basename(browser) 0064 try: 0065 command = _browsers[name.lower()] 0066 except KeyError: 0067 return [None, None] 0068 # now attempt to clone to fit the new name: 0069 controller = command[1] 0070 if controller and name.lower() == controller.basename: 0071 import copy 0072 controller = copy.copy(controller) 0073 controller.name = browser 0074 controller.basename = os.path.basename(browser) 0075 register(browser, None, controller) 0076 return [None, controller] 0077 return [None, None] 0078 0079 0080 def _iscommand(cmd): 0081 """Return True if cmd can be found on the executable search path.""" 0082 path = os.environ.get("PATH") 0083 if not path: 0084 return False 0085 for d in path.split(os.pathsep): 0086 exe = os.path.join(d, cmd) 0087 if os.path.isfile(exe): 0088 return True 0089 return False 0090 0091 0092 PROCESS_CREATION_DELAY = 4 0093 0094 0095 class GenericBrowser: 0096 def __init__(self, cmd): 0097 self.name, self.args = cmd.split(None, 1) 0098 self.basename = os.path.basename(self.name) 0099 0100 def open(self, url, new=0, autoraise=1): 0101 assert "'" not in url 0102 command = "%s %s" % (self.name, self.args) 0103 os.system(command % url) 0104 0105 def open_new(self, url): 0106 self.open(url) 0107 0108 0109 class Netscape: 0110 "Launcher class for Netscape browsers." 0111 def __init__(self, name): 0112 self.name = name 0113 self.basename = os.path.basename(name) 0114 0115 def _remote(self, action, autoraise): 0116 raise_opt = ("-noraise", "-raise")[autoraise] 0117 cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name, 0118 raise_opt, 0119 action) 0120 rc = os.system(cmd) 0121 if rc: 0122 import time 0123 os.system("%s &" % self.name) 0124 time.sleep(PROCESS_CREATION_DELAY) 0125 rc = os.system(cmd) 0126 return not rc 0127 0128 def open(self, url, new=0, autoraise=1): 0129 if new: 0130 self._remote("openURL(%s, new-window)"%url, autoraise) 0131 else: 0132 self._remote("openURL(%s)" % url, autoraise) 0133 0134 def open_new(self, url): 0135 self.open(url, 1) 0136 0137 0138 class Galeon: 0139 """Launcher class for Galeon browsers.""" 0140 def __init__(self, name): 0141 self.name = name 0142 self.basename = os.path.basename(name) 0143 0144 def _remote(self, action, autoraise): 0145 raise_opt = ("--noraise", "")[autoraise] 0146 cmd = "%s %s %s >/dev/null 2>&1" % (self.name, raise_opt, action) 0147 rc = os.system(cmd) 0148 if rc: 0149 import time 0150 os.system("%s >/dev/null 2>&1 &" % self.name) 0151 time.sleep(PROCESS_CREATION_DELAY) 0152 rc = os.system(cmd) 0153 return not rc 0154 0155 def open(self, url, new=0, autoraise=1): 0156 if new: 0157 self._remote("-w '%s'" % url, autoraise) 0158 else: 0159 self._remote("-n '%s'" % url, autoraise) 0160 0161 def open_new(self, url): 0162 self.open(url, 1) 0163 0164 0165 class Konqueror: 0166 """Controller for the KDE File Manager (kfm, or Konqueror). 0167 0168 See http://developer.kde.org/documentation/other/kfmclient.html 0169 for more information on the Konqueror remote-control interface. 0170 0171 """ 0172 def __init__(self): 0173 if _iscommand("konqueror"): 0174 self.name = self.basename = "konqueror" 0175 else: 0176 self.name = self.basename = "kfm" 0177 0178 def _remote(self, action): 0179 cmd = "kfmclient %s >/dev/null 2>&1" % action 0180 rc = os.system(cmd) 0181 if rc: 0182 import time 0183 if self.basename == "konqueror": 0184 os.system(self.name + " --silent &") 0185 else: 0186 os.system(self.name + " -d &") 0187 time.sleep(PROCESS_CREATION_DELAY) 0188 rc = os.system(cmd) 0189 return not rc 0190 0191 def open(self, url, new=1, autoraise=1): 0192 # XXX Currently I know no way to prevent KFM from 0193 # opening a new win. 0194 assert "'" not in url 0195 self._remote("openURL '%s'" % url) 0196 0197 open_new = open 0198 0199 0200 class Grail: 0201 # There should be a way to maintain a connection to Grail, but the 0202 # Grail remote control protocol doesn't really allow that at this 0203 # point. It probably neverwill! 0204 def _find_grail_rc(self): 0205 import glob 0206 import pwd 0207 import socket 0208 import tempfile 0209 tempdir = os.path.join(tempfile.gettempdir(), 0210 ".grail-unix") 0211 user = pwd.getpwuid(os.getuid())[0] 0212 filename = os.path.join(tempdir, user + "-*") 0213 maybes = glob.glob(filename) 0214 if not maybes: 0215 return None 0216 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 0217 for fn in maybes: 0218 # need to PING each one until we find one that's live 0219 try: 0220 s.connect(fn) 0221 except socket.error: 0222 # no good; attempt to clean it out, but don't fail: 0223 try: 0224 os.unlink(fn) 0225 except IOError: 0226 pass 0227 else: 0228 return s 0229 0230 def _remote(self, action): 0231 s = self._find_grail_rc() 0232 if not s: 0233 return 0 0234 s.send(action) 0235 s.close() 0236 return 1 0237 0238 def open(self, url, new=0, autoraise=1): 0239 if new: 0240 self._remote("LOADNEW " + url) 0241 else: 0242 self._remote("LOAD " + url) 0243 0244 def open_new(self, url): 0245 self.open(url, 1) 0246 0247 0248 class WindowsDefault: 0249 def open(self, url, new=0, autoraise=1): 0250 os.startfile(url) 0251 0252 def open_new(self, url): 0253 self.open(url) 0254 0255 # 0256 # Platform support for Unix 0257 # 0258 0259 # This is the right test because all these Unix browsers require either 0260 # a console terminal of an X display to run. Note that we cannot split 0261 # the TERM and DISPLAY cases, because we might be running Python from inside 0262 # an xterm. 0263 if os.environ.get("TERM") or os.environ.get("DISPLAY"): 0264 _tryorder = ["links", "lynx", "w3m"] 0265 0266 # Easy cases first -- register console browsers if we have them. 0267 if os.environ.get("TERM"): 0268 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/> 0269 if _iscommand("links"): 0270 register("links", None, GenericBrowser("links '%s'")) 0271 # The Lynx browser <http://lynx.browser.org/> 0272 if _iscommand("lynx"): 0273 register("lynx", None, GenericBrowser("lynx '%s'")) 0274 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/> 0275 if _iscommand("w3m"): 0276 register("w3m", None, GenericBrowser("w3m '%s'")) 0277 0278 # X browsers have more in the way of options 0279 if os.environ.get("DISPLAY"): 0280 _tryorder = ["galeon", "skipstone", 0281 "mozilla-firefox", "mozilla-firebird", "mozilla", "netscape", 0282 "kfm", "grail"] + _tryorder 0283 0284 # First, the Netscape series 0285 for browser in ("mozilla-firefox", "mozilla-firebird", 0286 "mozilla", "netscape"): 0287 if _iscommand(browser): 0288 register(browser, None, Netscape(browser)) 0289 0290 # Next, Mosaic -- old but still in use. 0291 if _iscommand("mosaic"): 0292 register("mosaic", None, GenericBrowser( 0293 "mosaic '%s' >/dev/null &")) 0294 0295 # Gnome's Galeon 0296 if _iscommand("galeon"): 0297 register("galeon", None, Galeon("galeon")) 0298 0299 # Skipstone, another Gtk/Mozilla based browser 0300 if _iscommand("skipstone"): 0301 register("skipstone", None, GenericBrowser( 0302 "skipstone '%s' >/dev/null &")) 0303 0304 # Konqueror/kfm, the KDE browser. 0305 if _iscommand("kfm") or _iscommand("konqueror"): 0306 register("kfm", Konqueror, Konqueror()) 0307 0308 # Grail, the Python browser. 0309 if _iscommand("grail"): 0310 register("grail", Grail, None) 0311 0312 0313 class InternetConfig: 0314 def open(self, url, new=0, autoraise=1): 0315 ic.launchurl(url) 0316 0317 def open_new(self, url): 0318 self.open(url) 0319 0320 0321 # 0322 # Platform support for Windows 0323 # 0324 0325 if sys.platform[:3] == "win": 0326 _tryorder = ["netscape", "windows-default"] 0327 register("windows-default", WindowsDefault) 0328 0329 # 0330 # Platform support for MacOS 0331 # 0332 0333 try: 0334 import ic 0335 except ImportError: 0336 pass 0337 else: 0338 # internet-config is the only supported controller on MacOS, 0339 # so don't mess with the default! 0340 _tryorder = ["internet-config"] 0341 register("internet-config", InternetConfig) 0342 0343 # 0344 # Platform support for OS/2 0345 # 0346 0347 if sys.platform[:3] == "os2" and _iscommand("netscape.exe"): 0348 _tryorder = ["os2netscape"] 0349 register("os2netscape", None, 0350 GenericBrowser("start netscape.exe %s")) 0351 0352 # OK, now that we know what the default preference orders for each 0353 # platform are, allow user to override them with the BROWSER variable. 0354 # 0355 if "BROWSER" in os.environ: 0356 # It's the user's responsibility to register handlers for any unknown 0357 # browser referenced by this value, before calling open(). 0358 _tryorder = os.environ["BROWSER"].split(os.pathsep) 0359 0360 for cmd in _tryorder: 0361 if not cmd.lower() in _browsers: 0362 if _iscommand(cmd.lower()): 0363 register(cmd.lower(), None, GenericBrowser( 0364 "%s '%%s'" % cmd.lower())) 0365 cmd = None # to make del work if _tryorder was empty 0366 del cmd 0367 0368 _tryorder = filter(lambda x: x.lower() in _browsers 0369 or x.find("%s") > -1, _tryorder) 0370 # what to do if _tryorder is now empty? 0371
Generated by PyXR 0.9.4