webbrowser.py revision 8719ad5ddefadbc08b56a0af91515f050c89c678
1e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney#! /usr/bin/env python 2e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney"""Interfaces for launching and remotely controlling Web browsers.""" 3e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney# Maintained by Georg Brandl. 4bf14e1077aa66ef1cb49bdaf06181de48bb2477fZeng, Star 5e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport io 6e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport os 7e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport shlex 8e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport sys 9e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport stat 10e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport subprocess 11e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyimport time 12e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 13e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] 14e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 15e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass Error(Exception): 16e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney pass 17e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 183c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen_browsers = {} # Dictionary of available browser controllers 193c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen_tryorder = [] # Preference order of available browsers 203c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 213c447c2760b3438a6d5ff0a7f2dbd580526452e5jchendef register(name, klass, instance=None, update_tryorder=1): 223c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen """Register a browser connector and, optionally, connection.""" 23e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney _browsers[name.lower()] = [klass, instance] 24e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if update_tryorder > 0: 25e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney _tryorder.append(name) 26e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney elif update_tryorder < 0: 27e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney _tryorder.insert(0, name) 28e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 29e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneydef get(using=None): 30e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Return a browser launcher instance appropriate for the environment.""" 31e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if using is not None: 32e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney alternatives = [using] 33e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 34e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney alternatives = _tryorder 35e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for browser in alternatives: 36e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if '%s' in browser: 373c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen # User gave us a command line, split it into name and args 383c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen browser = shlex.split(browser) 393c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen if browser[-1] == '&': 403c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen return BackgroundBrowser(browser[:-1]) 41e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 42e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return GenericBrowser(browser) 43e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 44e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # User gave us a browser name or path. 45e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney try: 46e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney command = _browsers[browser.lower()] 47e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney except KeyError: 483c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen command = _synthesize(browser) 493c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen if command[1] is not None: 503c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen return command[1] 513c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen elif command[0] is not None: 523c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen return command[0]() 533c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen raise Error("could not locate runnable browser") 543c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 553c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen# Please note: the following definition hides a builtin function. 563c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen# It is recommended one does "import webbrowser" and uses webbrowser.open(url) 573c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen# instead of "from webbrowser import *". 583c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 593c447c2760b3438a6d5ff0a7f2dbd580526452e5jchendef open(url, new=0, autoraise=True): 603c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen for name in _tryorder: 613c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen browser = get(name) 622c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun if browser.open(url, new, autoraise): 632c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun return True 642c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun return False 652c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun 662c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsundef open_new(url): 672c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun return open(url, 1) 682c0f06f0b80eebec5ec4a19d6d4440ba17bbcb0drsun 693c447c2760b3438a6d5ff0a7f2dbd580526452e5jchendef open_new_tab(url): 703c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen return open(url, 2) 713c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 723c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 733c447c2760b3438a6d5ff0a7f2dbd580526452e5jchendef _synthesize(browser, update_tryorder=1): 743c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen """Attempt to synthesize a controller base on existing controllers. 753c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 763c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen This is useful to create a controller when a user specifies a path to 773c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen an entry in the BROWSER environment variable -- we can copy a general 783c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen controller to operate using a specific installation of the desired 793c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen browser in this way. 803c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 813c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen If we can't create a controller in this way, or if there is no 823c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen executable for the requested browser, return [None, None]. 833c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen 843c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen """ 853c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen cmd = browser.split()[0] 863c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen if not _iscommand(cmd): 87e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return [None, None] 88e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney name = os.path.basename(cmd) 89e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney try: 90e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney command = _browsers[name.lower()] 91e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney except KeyError: 92e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return [None, None] 93e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # now attempt to clone to fit the new name: 94e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney controller = command[1] 95e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if controller and name.lower() == controller.basename: 96e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney import copy 97e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney controller = copy.copy(controller) 983c447c2760b3438a6d5ff0a7f2dbd580526452e5jchen controller.name = browser 99e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney controller.basename = os.path.basename(browser) 100e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney register(browser, None, controller, update_tryorder) 101e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return [None, controller] 102e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return [None, None] 103e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 104e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 105e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyif sys.platform[:3] == "win": 106e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def _isexecutable(cmd): 107e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney cmd = cmd.lower() 108e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")): 109e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return True 110e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for ext in ".exe", ".bat": 111e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if os.path.isfile(cmd + ext): 112e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return True 113e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return False 114e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyelse: 115e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def _isexecutable(cmd): 116e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if os.path.isfile(cmd): 11784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng mode = os.stat(cmd)[stat.ST_MODE] 11884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH: 11984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return True 120e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return False 12152c0d06b94665def4977e13ea329dccb17f46da5hhuan 122e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneydef _iscommand(cmd): 123bf14e1077aa66ef1cb49bdaf06181de48bb2477fZeng, Star """Return True if cmd is executable or can be found on the executable 124e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney search path.""" 12584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if _isexecutable(cmd): 12684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return True 1275b422a7bbd06015d76cdb41cf8c377f7a898efa9hhuan path = os.environ.get("PATH") 128e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if not path: 12984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return False 130e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for d in path.split(os.pathsep): 131e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney exe = os.path.join(d, cmd) 132e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if _isexecutable(exe): 133e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return True 134e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return False 135e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 136e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 137e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney# General parent classes 138e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 139e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass BaseBrowser(object): 140e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Parent class for all browsers. Do not use directly.""" 141e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 142e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney args = ['%s'] 143e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 144e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def __init__(self, name=""): 145e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.name = name 146e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.basename = name 147e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 148e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def open(self, url, new=0, autoraise=True): 149e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney raise NotImplementedError 150e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 151e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def open_new(self, url): 152e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return self.open(url, 1) 153e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 154e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def open_new_tab(self, url): 155e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return self.open(url, 2) 156e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 157e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 158e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass GenericBrowser(BaseBrowser): 159e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Class for all browsers started with a command 160e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney and without remote functionality.""" 161e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 162e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def __init__(self, name): 163e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if isinstance(name, str): 164e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.name = name 165e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.args = ["%s"] 166e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 167e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # name should be a list with arguments 168e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.name = name[0] 169e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.args = name[1:] 170e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney self.basename = os.path.basename(self.name) 171fbe12b79aef4c2706e90078cc75b94dcf7926ba8ydong 172e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def open(self, url, new=0, autoraise=True): 1735b422a7bbd06015d76cdb41cf8c377f7a898efa9hhuan cmdline = [self.name] + [arg.replace("%s", url) 174e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for arg in self.args] 175e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney try: 176e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if sys.platform[:3] == 'win': 177e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney p = subprocess.Popen(cmdline) 178e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 179e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney p = subprocess.Popen(cmdline, close_fds=True) 180e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return not p.wait() 181e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney except OSError: 182e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return False 183e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 184e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 185e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass BackgroundBrowser(GenericBrowser): 186e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Class for all browsers which are to be started in the 187e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney background.""" 188e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 189e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def open(self, url, new=0, autoraise=True): 190e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney cmdline = [self.name] + [arg.replace("%s", url) 191e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for arg in self.args] 192e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney try: 19384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if sys.platform[:3] == 'win': 194e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney p = subprocess.Popen(cmdline) 195e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 196e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney setsid = getattr(os, 'setsid', None) 197e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if not setsid: 198e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney setsid = getattr(os, 'setpgrp', None) 199e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid) 200e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return (p.poll() is None) 201e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney except OSError: 202e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return False 203e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 204e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 205e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass UnixBrowser(BaseBrowser): 206e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Parent class for all Unix browsers with remote functionality.""" 207e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 208e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney raise_opts = None 209e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_args = ['%action', '%s'] 210e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action = None 211e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action_newwin = None 212e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action_newtab = None 21384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng background = False 214e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney redirect_stdout = True 215e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 216e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney def _invoke(self, args, remote, autoraise): 217e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney raise_opt = [] 218e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if remote and self.raise_opts: 219e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # use autoraise argument only for remote invocation 220e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney autoraise = int(autoraise) 221e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney opt = self.raise_opts[autoraise] 222e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if opt: raise_opt = [opt] 223e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 224e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney cmdline = [self.name] + raise_opt + args 225e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 226fbe12b79aef4c2706e90078cc75b94dcf7926ba8ydong if remote or self.background: 227e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney inout = io.open(os.devnull, "r+") 228e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 229e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # for TTY browsers, we need stdin/out 230e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney inout = None 231e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # if possible, put browser in separate process group, so 232bf14e1077aa66ef1cb49bdaf06181de48bb2477fZeng, Star # keyboard interrupts don't affect browser as well as Python 233bf14e1077aa66ef1cb49bdaf06181de48bb2477fZeng, Star setsid = getattr(os, 'setsid', None) 234bf14e1077aa66ef1cb49bdaf06181de48bb2477fZeng, Star if not setsid: 235e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney setsid = getattr(os, 'setpgrp', None) 236e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 237e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney p = subprocess.Popen(cmdline, close_fds=True, stdin=inout, 238e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney stdout=(self.redirect_stdout and inout or None), 23984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng stderr=inout, preexec_fn=setsid) 24084edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if remote: 24184edd20bd0756ef5719835498d4283435d6b5e77Star Zeng # wait five secons. If the subprocess is not finished, the 24284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng # remote invocation has (hopefully) started a new instance. 24384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng time.sleep(1) 24484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng rc = p.poll() 24584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if rc is None: 24684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng time.sleep(4) 24784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng rc = p.poll() 24884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if rc is None: 24984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return True 25084edd20bd0756ef5719835498d4283435d6b5e77Star Zeng # if remote call failed, open() will try direct invocation 25184edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return not rc 25284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng elif self.background: 25384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if p.poll() is None: 25484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return True 25584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng else: 25684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return False 25784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng else: 25884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng return not p.wait() 25984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 26084edd20bd0756ef5719835498d4283435d6b5e77Star Zeng def open(self, url, new=0, autoraise=True): 26184edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if new == 0: 26284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng action = self.remote_action 26384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng elif new == 1: 26484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng action = self.remote_action_newwin 26584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng elif new == 2: 26684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng if self.remote_action_newtab is None: 26784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng action = self.remote_action_newwin 26884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng else: 269e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney action = self.remote_action_newtab 270e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 271e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney raise Error("Bad 'new' parameter to open(); " + 272e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney "expected 0, 1, or 2, got %s" % new) 273e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 274e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney args = [arg.replace("%s", url).replace("%action", action) 275e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney for arg in self.remote_args] 276e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney success = self._invoke(args, True, autoraise) 277e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney if not success: 278e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney # remote invocation failed, try straight way 27984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng args = [arg.replace("%s", url) for arg in self.args] 280e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return self._invoke(args, False, False) 281e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney else: 282e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney return True 283e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 284e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 285e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass Mozilla(UnixBrowser): 286e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Launcher class for Mozilla/Netscape browsers.""" 287e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 288e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney raise_opts = ["-noraise", "-raise"] 289e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 290e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_args = ['-remote', 'openURL(%s%action)'] 291e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action = "" 292e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action_newwin = ",new-window" 293e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney remote_action_newtab = ",new-tab" 294e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 29584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng background = True 296e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 297e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyNetscape = Mozilla 298e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 299e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney 300e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinneyclass Galeon(UnixBrowser): 301e42e94041f7c71a5e2e57154bd568f3c14fd6eecmdkinney """Launcher class for Galeon/Epiphany browsers.""" 30284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 30384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng raise_opts = ["-noraise", ""] 30484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_args = ['%action', '%s'] 30584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_action = "-n" 30684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_action_newwin = "-w" 30784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 30884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng background = True 30984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 31084edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 31184edd20bd0756ef5719835498d4283435d6b5e77Star Zengclass Opera(UnixBrowser): 31284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng "Launcher class for Opera browser." 31384edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 31484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng raise_opts = ["", "-raise"] 31584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 31684edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_args = ['-remote', 'openURL(%s%action)'] 31784edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_action = "" 31884edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_action_newwin = ",new-window" 31984edd20bd0756ef5719835498d4283435d6b5e77Star Zeng remote_action_newtab = ",new-page" 32084edd20bd0756ef5719835498d4283435d6b5e77Star Zeng background = True 32184edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 32284edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 32384edd20bd0756ef5719835498d4283435d6b5e77Star Zengclass Elinks(UnixBrowser): 32484edd20bd0756ef5719835498d4283435d6b5e77Star Zeng "Launcher class for Elinks browsers." 32584edd20bd0756ef5719835498d4283435d6b5e77Star Zeng 326 remote_args = ['-remote', 'openURL(%s%action)'] 327 remote_action = "" 328 remote_action_newwin = ",new-window" 329 remote_action_newtab = ",new-tab" 330 background = False 331 332 # elinks doesn't like its stdout to be redirected - 333 # it uses redirected stdout as a signal to do -dump 334 redirect_stdout = False 335 336 337class Konqueror(BaseBrowser): 338 """Controller for the KDE File Manager (kfm, or Konqueror). 339 340 See the output of ``kfmclient --commands`` 341 for more information on the Konqueror remote-control interface. 342 """ 343 344 def open(self, url, new=0, autoraise=True): 345 # XXX Currently I know no way to prevent KFM from opening a new win. 346 if new == 2: 347 action = "newTab" 348 else: 349 action = "openURL" 350 351 devnull = io.open(os.devnull, "r+") 352 # if possible, put browser in separate process group, so 353 # keyboard interrupts don't affect browser as well as Python 354 setsid = getattr(os, 'setsid', None) 355 if not setsid: 356 setsid = getattr(os, 'setpgrp', None) 357 358 try: 359 p = subprocess.Popen(["kfmclient", action, url], 360 close_fds=True, stdin=devnull, 361 stdout=devnull, stderr=devnull) 362 except OSError: 363 # fall through to next variant 364 pass 365 else: 366 p.wait() 367 # kfmclient's return code unfortunately has no meaning as it seems 368 return True 369 370 try: 371 p = subprocess.Popen(["konqueror", "--silent", url], 372 close_fds=True, stdin=devnull, 373 stdout=devnull, stderr=devnull, 374 preexec_fn=setsid) 375 except OSError: 376 # fall through to next variant 377 pass 378 else: 379 if p.poll() is None: 380 # Should be running now. 381 return True 382 383 try: 384 p = subprocess.Popen(["kfm", "-d", url], 385 close_fds=True, stdin=devnull, 386 stdout=devnull, stderr=devnull, 387 preexec_fn=setsid) 388 except OSError: 389 return False 390 else: 391 return (p.poll() is None) 392 393 394class Grail(BaseBrowser): 395 # There should be a way to maintain a connection to Grail, but the 396 # Grail remote control protocol doesn't really allow that at this 397 # point. It probably never will! 398 def _find_grail_rc(self): 399 import glob 400 import pwd 401 import socket 402 import tempfile 403 tempdir = os.path.join(tempfile.gettempdir(), 404 ".grail-unix") 405 user = pwd.getpwuid(os.getuid())[0] 406 filename = os.path.join(tempdir, user + "-*") 407 maybes = glob.glob(filename) 408 if not maybes: 409 return None 410 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 411 for fn in maybes: 412 # need to PING each one until we find one that's live 413 try: 414 s.connect(fn) 415 except socket.error: 416 # no good; attempt to clean it out, but don't fail: 417 try: 418 os.unlink(fn) 419 except IOError: 420 pass 421 else: 422 return s 423 424 def _remote(self, action): 425 s = self._find_grail_rc() 426 if not s: 427 return 0 428 s.send(action) 429 s.close() 430 return 1 431 432 def open(self, url, new=0, autoraise=True): 433 if new: 434 ok = self._remote("LOADNEW " + url) 435 else: 436 ok = self._remote("LOAD " + url) 437 return ok 438 439 440# 441# Platform support for Unix 442# 443 444# These are the right tests because all these Unix browsers require either 445# a console terminal or an X display to run. 446 447def register_X_browsers(): 448 449 # The default GNOME browser 450 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"): 451 register("gnome-open", None, BackgroundBrowser("gnome-open")) 452 453 # The default KDE browser 454 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"): 455 register("kfmclient", Konqueror, Konqueror("kfmclient")) 456 457 # The Mozilla/Netscape browsers 458 for browser in ("mozilla-firefox", "firefox", 459 "mozilla-firebird", "firebird", 460 "seamonkey", "mozilla", "netscape"): 461 if _iscommand(browser): 462 register(browser, None, Mozilla(browser)) 463 464 # Konqueror/kfm, the KDE browser. 465 if _iscommand("kfm"): 466 register("kfm", Konqueror, Konqueror("kfm")) 467 elif _iscommand("konqueror"): 468 register("konqueror", Konqueror, Konqueror("konqueror")) 469 470 # Gnome's Galeon and Epiphany 471 for browser in ("galeon", "epiphany"): 472 if _iscommand(browser): 473 register(browser, None, Galeon(browser)) 474 475 # Skipstone, another Gtk/Mozilla based browser 476 if _iscommand("skipstone"): 477 register("skipstone", None, BackgroundBrowser("skipstone")) 478 479 # Opera, quite popular 480 if _iscommand("opera"): 481 register("opera", None, Opera("opera")) 482 483 # Next, Mosaic -- old but still in use. 484 if _iscommand("mosaic"): 485 register("mosaic", None, BackgroundBrowser("mosaic")) 486 487 # Grail, the Python browser. Does anybody still use it? 488 if _iscommand("grail"): 489 register("grail", Grail, None) 490 491# Prefer X browsers if present 492if os.environ.get("DISPLAY"): 493 register_X_browsers() 494 495# Also try console browsers 496if os.environ.get("TERM"): 497 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/> 498 if _iscommand("links"): 499 register("links", None, GenericBrowser("links")) 500 if _iscommand("elinks"): 501 register("elinks", None, Elinks("elinks")) 502 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/> 503 if _iscommand("lynx"): 504 register("lynx", None, GenericBrowser("lynx")) 505 # The w3m browser <http://w3m.sourceforge.net/> 506 if _iscommand("w3m"): 507 register("w3m", None, GenericBrowser("w3m")) 508 509# 510# Platform support for Windows 511# 512 513if sys.platform[:3] == "win": 514 class WindowsDefault(BaseBrowser): 515 def open(self, url, new=0, autoraise=True): 516 try: 517 os.startfile(url) 518 except WindowsError: 519 # [Error 22] No application is associated with the specified 520 # file for this operation: '<URL>' 521 return False 522 else: 523 return True 524 525 _tryorder = [] 526 _browsers = {} 527 528 # First try to use the default Windows browser 529 register("windows-default", WindowsDefault) 530 531 # Detect some common Windows browsers, fallback to IE 532 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), 533 "Internet Explorer\\IEXPLORE.EXE") 534 for browser in ("firefox", "firebird", "seamonkey", "mozilla", 535 "netscape", "opera", iexplore): 536 if _iscommand(browser): 537 register(browser, None, BackgroundBrowser(browser)) 538 539# 540# Platform support for MacOS 541# 542 543try: 544 import ic 545except ImportError: 546 pass 547else: 548 class InternetConfig(BaseBrowser): 549 def open(self, url, new=0, autoraise=True): 550 ic.launchurl(url) 551 return True # Any way to get status? 552 553 register("internet-config", InternetConfig, update_tryorder=-1) 554 555if sys.platform == 'darwin': 556 # Adapted from patch submitted to SourceForge by Steven J. Burr 557 class MacOSX(BaseBrowser): 558 """Launcher class for Aqua browsers on Mac OS X 559 560 Optionally specify a browser name on instantiation. Note that this 561 will not work for Aqua browsers if the user has moved the application 562 package after installation. 563 564 If no browser is specified, the default browser, as specified in the 565 Internet System Preferences panel, will be used. 566 """ 567 def __init__(self, name): 568 self.name = name 569 570 def open(self, url, new=0, autoraise=True): 571 assert "'" not in url 572 # hack for local urls 573 if not ':' in url: 574 url = 'file:'+url 575 576 # new must be 0 or 1 577 new = int(bool(new)) 578 if self.name == "default": 579 # User called open, open_new or get without a browser parameter 580 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser 581 else: 582 # User called get and chose a browser 583 if self.name == "OmniWeb": 584 toWindow = "" 585 else: 586 # Include toWindow parameter of OpenURL command for browsers 587 # that support it. 0 == new window; -1 == existing 588 toWindow = "toWindow %d" % (new - 1) 589 cmd = 'OpenURL "%s"' % url.replace('"', '%22') 590 script = '''tell application "%s" 591 activate 592 %s %s 593 end tell''' % (self.name, cmd, toWindow) 594 # Open pipe to AppleScript through osascript command 595 osapipe = os.popen("osascript", "w") 596 if osapipe is None: 597 return False 598 # Write script to osascript's stdin 599 osapipe.write(script) 600 rc = osapipe.close() 601 return not rc 602 603 # Don't clear _tryorder or _browsers since OS X can use above Unix support 604 # (but we prefer using the OS X specific stuff) 605 register("MacOSX", None, MacOSX('default'), -1) 606 607 608# 609# Platform support for OS/2 610# 611 612if sys.platform[:3] == "os2" and _iscommand("netscape"): 613 _tryorder = [] 614 _browsers = {} 615 register("os2netscape", None, 616 GenericBrowser(["start", "netscape", "%s"]), -1) 617 618 619# OK, now that we know what the default preference orders for each 620# platform are, allow user to override them with the BROWSER variable. 621if "BROWSER" in os.environ: 622 _userchoices = os.environ["BROWSER"].split(os.pathsep) 623 _userchoices.reverse() 624 625 # Treat choices in same way as if passed into get() but do register 626 # and prepend to _tryorder 627 for cmdline in _userchoices: 628 if cmdline != '': 629 cmd = _synthesize(cmdline, -1) 630 if cmd[1] is None: 631 register(cmdline, None, GenericBrowser(cmdline), -1) 632 cmdline = None # to make del work if _userchoices was empty 633 del cmdline 634 del _userchoices 635 636# what to do if _tryorder is now empty? 637 638 639def main(): 640 import getopt 641 usage = """Usage: %s [-n | -t] url 642 -n: open new window 643 -t: open new tab""" % sys.argv[0] 644 try: 645 opts, args = getopt.getopt(sys.argv[1:], 'ntd') 646 except getopt.error as msg: 647 print(msg, file=sys.stderr) 648 print(usage, file=sys.stderr) 649 sys.exit(1) 650 new_win = 0 651 for o, a in opts: 652 if o == '-n': new_win = 1 653 elif o == '-t': new_win = 2 654 if len(args) != 1: 655 print(usage, file=sys.stderr) 656 sys.exit(1) 657 658 url = args[0] 659 open(url, new_win) 660 661 print("\a") 662 663if __name__ == "__main__": 664 main() 665