1*cda5da8dSAndroid Build Coastguard Worker#! /usr/bin/env python3 2*cda5da8dSAndroid Build Coastguard Worker"""Interfaces for launching and remotely controlling web browsers.""" 3*cda5da8dSAndroid Build Coastguard Worker# Maintained by Georg Brandl. 4*cda5da8dSAndroid Build Coastguard Worker 5*cda5da8dSAndroid Build Coastguard Workerimport os 6*cda5da8dSAndroid Build Coastguard Workerimport shlex 7*cda5da8dSAndroid Build Coastguard Workerimport shutil 8*cda5da8dSAndroid Build Coastguard Workerimport sys 9*cda5da8dSAndroid Build Coastguard Workerimport subprocess 10*cda5da8dSAndroid Build Coastguard Workerimport threading 11*cda5da8dSAndroid Build Coastguard Workerimport warnings 12*cda5da8dSAndroid Build Coastguard Worker 13*cda5da8dSAndroid Build Coastguard Worker__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] 14*cda5da8dSAndroid Build Coastguard Worker 15*cda5da8dSAndroid Build Coastguard Workerclass Error(Exception): 16*cda5da8dSAndroid Build Coastguard Worker pass 17*cda5da8dSAndroid Build Coastguard Worker 18*cda5da8dSAndroid Build Coastguard Worker_lock = threading.RLock() 19*cda5da8dSAndroid Build Coastguard Worker_browsers = {} # Dictionary of available browser controllers 20*cda5da8dSAndroid Build Coastguard Worker_tryorder = None # Preference order of available browsers 21*cda5da8dSAndroid Build Coastguard Worker_os_preferred_browser = None # The preferred browser 22*cda5da8dSAndroid Build Coastguard Worker 23*cda5da8dSAndroid Build Coastguard Workerdef register(name, klass, instance=None, *, preferred=False): 24*cda5da8dSAndroid Build Coastguard Worker """Register a browser connector.""" 25*cda5da8dSAndroid Build Coastguard Worker with _lock: 26*cda5da8dSAndroid Build Coastguard Worker if _tryorder is None: 27*cda5da8dSAndroid Build Coastguard Worker register_standard_browsers() 28*cda5da8dSAndroid Build Coastguard Worker _browsers[name.lower()] = [klass, instance] 29*cda5da8dSAndroid Build Coastguard Worker 30*cda5da8dSAndroid Build Coastguard Worker # Preferred browsers go to the front of the list. 31*cda5da8dSAndroid Build Coastguard Worker # Need to match to the default browser returned by xdg-settings, which 32*cda5da8dSAndroid Build Coastguard Worker # may be of the form e.g. "firefox.desktop". 33*cda5da8dSAndroid Build Coastguard Worker if preferred or (_os_preferred_browser and name in _os_preferred_browser): 34*cda5da8dSAndroid Build Coastguard Worker _tryorder.insert(0, name) 35*cda5da8dSAndroid Build Coastguard Worker else: 36*cda5da8dSAndroid Build Coastguard Worker _tryorder.append(name) 37*cda5da8dSAndroid Build Coastguard Worker 38*cda5da8dSAndroid Build Coastguard Workerdef get(using=None): 39*cda5da8dSAndroid Build Coastguard Worker """Return a browser launcher instance appropriate for the environment.""" 40*cda5da8dSAndroid Build Coastguard Worker if _tryorder is None: 41*cda5da8dSAndroid Build Coastguard Worker with _lock: 42*cda5da8dSAndroid Build Coastguard Worker if _tryorder is None: 43*cda5da8dSAndroid Build Coastguard Worker register_standard_browsers() 44*cda5da8dSAndroid Build Coastguard Worker if using is not None: 45*cda5da8dSAndroid Build Coastguard Worker alternatives = [using] 46*cda5da8dSAndroid Build Coastguard Worker else: 47*cda5da8dSAndroid Build Coastguard Worker alternatives = _tryorder 48*cda5da8dSAndroid Build Coastguard Worker for browser in alternatives: 49*cda5da8dSAndroid Build Coastguard Worker if '%s' in browser: 50*cda5da8dSAndroid Build Coastguard Worker # User gave us a command line, split it into name and args 51*cda5da8dSAndroid Build Coastguard Worker browser = shlex.split(browser) 52*cda5da8dSAndroid Build Coastguard Worker if browser[-1] == '&': 53*cda5da8dSAndroid Build Coastguard Worker return BackgroundBrowser(browser[:-1]) 54*cda5da8dSAndroid Build Coastguard Worker else: 55*cda5da8dSAndroid Build Coastguard Worker return GenericBrowser(browser) 56*cda5da8dSAndroid Build Coastguard Worker else: 57*cda5da8dSAndroid Build Coastguard Worker # User gave us a browser name or path. 58*cda5da8dSAndroid Build Coastguard Worker try: 59*cda5da8dSAndroid Build Coastguard Worker command = _browsers[browser.lower()] 60*cda5da8dSAndroid Build Coastguard Worker except KeyError: 61*cda5da8dSAndroid Build Coastguard Worker command = _synthesize(browser) 62*cda5da8dSAndroid Build Coastguard Worker if command[1] is not None: 63*cda5da8dSAndroid Build Coastguard Worker return command[1] 64*cda5da8dSAndroid Build Coastguard Worker elif command[0] is not None: 65*cda5da8dSAndroid Build Coastguard Worker return command[0]() 66*cda5da8dSAndroid Build Coastguard Worker raise Error("could not locate runnable browser") 67*cda5da8dSAndroid Build Coastguard Worker 68*cda5da8dSAndroid Build Coastguard Worker# Please note: the following definition hides a builtin function. 69*cda5da8dSAndroid Build Coastguard Worker# It is recommended one does "import webbrowser" and uses webbrowser.open(url) 70*cda5da8dSAndroid Build Coastguard Worker# instead of "from webbrowser import *". 71*cda5da8dSAndroid Build Coastguard Worker 72*cda5da8dSAndroid Build Coastguard Workerdef open(url, new=0, autoraise=True): 73*cda5da8dSAndroid Build Coastguard Worker """Display url using the default browser. 74*cda5da8dSAndroid Build Coastguard Worker 75*cda5da8dSAndroid Build Coastguard Worker If possible, open url in a location determined by new. 76*cda5da8dSAndroid Build Coastguard Worker - 0: the same browser window (the default). 77*cda5da8dSAndroid Build Coastguard Worker - 1: a new browser window. 78*cda5da8dSAndroid Build Coastguard Worker - 2: a new browser page ("tab"). 79*cda5da8dSAndroid Build Coastguard Worker If possible, autoraise raises the window (the default) or not. 80*cda5da8dSAndroid Build Coastguard Worker """ 81*cda5da8dSAndroid Build Coastguard Worker if _tryorder is None: 82*cda5da8dSAndroid Build Coastguard Worker with _lock: 83*cda5da8dSAndroid Build Coastguard Worker if _tryorder is None: 84*cda5da8dSAndroid Build Coastguard Worker register_standard_browsers() 85*cda5da8dSAndroid Build Coastguard Worker for name in _tryorder: 86*cda5da8dSAndroid Build Coastguard Worker browser = get(name) 87*cda5da8dSAndroid Build Coastguard Worker if browser.open(url, new, autoraise): 88*cda5da8dSAndroid Build Coastguard Worker return True 89*cda5da8dSAndroid Build Coastguard Worker return False 90*cda5da8dSAndroid Build Coastguard Worker 91*cda5da8dSAndroid Build Coastguard Workerdef open_new(url): 92*cda5da8dSAndroid Build Coastguard Worker """Open url in a new window of the default browser. 93*cda5da8dSAndroid Build Coastguard Worker 94*cda5da8dSAndroid Build Coastguard Worker If not possible, then open url in the only browser window. 95*cda5da8dSAndroid Build Coastguard Worker """ 96*cda5da8dSAndroid Build Coastguard Worker return open(url, 1) 97*cda5da8dSAndroid Build Coastguard Worker 98*cda5da8dSAndroid Build Coastguard Workerdef open_new_tab(url): 99*cda5da8dSAndroid Build Coastguard Worker """Open url in a new page ("tab") of the default browser. 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker If not possible, then the behavior becomes equivalent to open_new(). 102*cda5da8dSAndroid Build Coastguard Worker """ 103*cda5da8dSAndroid Build Coastguard Worker return open(url, 2) 104*cda5da8dSAndroid Build Coastguard Worker 105*cda5da8dSAndroid Build Coastguard Worker 106*cda5da8dSAndroid Build Coastguard Workerdef _synthesize(browser, *, preferred=False): 107*cda5da8dSAndroid Build Coastguard Worker """Attempt to synthesize a controller based on existing controllers. 108*cda5da8dSAndroid Build Coastguard Worker 109*cda5da8dSAndroid Build Coastguard Worker This is useful to create a controller when a user specifies a path to 110*cda5da8dSAndroid Build Coastguard Worker an entry in the BROWSER environment variable -- we can copy a general 111*cda5da8dSAndroid Build Coastguard Worker controller to operate using a specific installation of the desired 112*cda5da8dSAndroid Build Coastguard Worker browser in this way. 113*cda5da8dSAndroid Build Coastguard Worker 114*cda5da8dSAndroid Build Coastguard Worker If we can't create a controller in this way, or if there is no 115*cda5da8dSAndroid Build Coastguard Worker executable for the requested browser, return [None, None]. 116*cda5da8dSAndroid Build Coastguard Worker 117*cda5da8dSAndroid Build Coastguard Worker """ 118*cda5da8dSAndroid Build Coastguard Worker cmd = browser.split()[0] 119*cda5da8dSAndroid Build Coastguard Worker if not shutil.which(cmd): 120*cda5da8dSAndroid Build Coastguard Worker return [None, None] 121*cda5da8dSAndroid Build Coastguard Worker name = os.path.basename(cmd) 122*cda5da8dSAndroid Build Coastguard Worker try: 123*cda5da8dSAndroid Build Coastguard Worker command = _browsers[name.lower()] 124*cda5da8dSAndroid Build Coastguard Worker except KeyError: 125*cda5da8dSAndroid Build Coastguard Worker return [None, None] 126*cda5da8dSAndroid Build Coastguard Worker # now attempt to clone to fit the new name: 127*cda5da8dSAndroid Build Coastguard Worker controller = command[1] 128*cda5da8dSAndroid Build Coastguard Worker if controller and name.lower() == controller.basename: 129*cda5da8dSAndroid Build Coastguard Worker import copy 130*cda5da8dSAndroid Build Coastguard Worker controller = copy.copy(controller) 131*cda5da8dSAndroid Build Coastguard Worker controller.name = browser 132*cda5da8dSAndroid Build Coastguard Worker controller.basename = os.path.basename(browser) 133*cda5da8dSAndroid Build Coastguard Worker register(browser, None, instance=controller, preferred=preferred) 134*cda5da8dSAndroid Build Coastguard Worker return [None, controller] 135*cda5da8dSAndroid Build Coastguard Worker return [None, None] 136*cda5da8dSAndroid Build Coastguard Worker 137*cda5da8dSAndroid Build Coastguard Worker 138*cda5da8dSAndroid Build Coastguard Worker# General parent classes 139*cda5da8dSAndroid Build Coastguard Worker 140*cda5da8dSAndroid Build Coastguard Workerclass BaseBrowser(object): 141*cda5da8dSAndroid Build Coastguard Worker """Parent class for all browsers. Do not use directly.""" 142*cda5da8dSAndroid Build Coastguard Worker 143*cda5da8dSAndroid Build Coastguard Worker args = ['%s'] 144*cda5da8dSAndroid Build Coastguard Worker 145*cda5da8dSAndroid Build Coastguard Worker def __init__(self, name=""): 146*cda5da8dSAndroid Build Coastguard Worker self.name = name 147*cda5da8dSAndroid Build Coastguard Worker self.basename = name 148*cda5da8dSAndroid Build Coastguard Worker 149*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 150*cda5da8dSAndroid Build Coastguard Worker raise NotImplementedError 151*cda5da8dSAndroid Build Coastguard Worker 152*cda5da8dSAndroid Build Coastguard Worker def open_new(self, url): 153*cda5da8dSAndroid Build Coastguard Worker return self.open(url, 1) 154*cda5da8dSAndroid Build Coastguard Worker 155*cda5da8dSAndroid Build Coastguard Worker def open_new_tab(self, url): 156*cda5da8dSAndroid Build Coastguard Worker return self.open(url, 2) 157*cda5da8dSAndroid Build Coastguard Worker 158*cda5da8dSAndroid Build Coastguard Worker 159*cda5da8dSAndroid Build Coastguard Workerclass GenericBrowser(BaseBrowser): 160*cda5da8dSAndroid Build Coastguard Worker """Class for all browsers started with a command 161*cda5da8dSAndroid Build Coastguard Worker and without remote functionality.""" 162*cda5da8dSAndroid Build Coastguard Worker 163*cda5da8dSAndroid Build Coastguard Worker def __init__(self, name): 164*cda5da8dSAndroid Build Coastguard Worker if isinstance(name, str): 165*cda5da8dSAndroid Build Coastguard Worker self.name = name 166*cda5da8dSAndroid Build Coastguard Worker self.args = ["%s"] 167*cda5da8dSAndroid Build Coastguard Worker else: 168*cda5da8dSAndroid Build Coastguard Worker # name should be a list with arguments 169*cda5da8dSAndroid Build Coastguard Worker self.name = name[0] 170*cda5da8dSAndroid Build Coastguard Worker self.args = name[1:] 171*cda5da8dSAndroid Build Coastguard Worker self.basename = os.path.basename(self.name) 172*cda5da8dSAndroid Build Coastguard Worker 173*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 174*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 175*cda5da8dSAndroid Build Coastguard Worker cmdline = [self.name] + [arg.replace("%s", url) 176*cda5da8dSAndroid Build Coastguard Worker for arg in self.args] 177*cda5da8dSAndroid Build Coastguard Worker try: 178*cda5da8dSAndroid Build Coastguard Worker if sys.platform[:3] == 'win': 179*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(cmdline) 180*cda5da8dSAndroid Build Coastguard Worker else: 181*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(cmdline, close_fds=True) 182*cda5da8dSAndroid Build Coastguard Worker return not p.wait() 183*cda5da8dSAndroid Build Coastguard Worker except OSError: 184*cda5da8dSAndroid Build Coastguard Worker return False 185*cda5da8dSAndroid Build Coastguard Worker 186*cda5da8dSAndroid Build Coastguard Worker 187*cda5da8dSAndroid Build Coastguard Workerclass BackgroundBrowser(GenericBrowser): 188*cda5da8dSAndroid Build Coastguard Worker """Class for all browsers which are to be started in the 189*cda5da8dSAndroid Build Coastguard Worker background.""" 190*cda5da8dSAndroid Build Coastguard Worker 191*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 192*cda5da8dSAndroid Build Coastguard Worker cmdline = [self.name] + [arg.replace("%s", url) 193*cda5da8dSAndroid Build Coastguard Worker for arg in self.args] 194*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 195*cda5da8dSAndroid Build Coastguard Worker try: 196*cda5da8dSAndroid Build Coastguard Worker if sys.platform[:3] == 'win': 197*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(cmdline) 198*cda5da8dSAndroid Build Coastguard Worker else: 199*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(cmdline, close_fds=True, 200*cda5da8dSAndroid Build Coastguard Worker start_new_session=True) 201*cda5da8dSAndroid Build Coastguard Worker return (p.poll() is None) 202*cda5da8dSAndroid Build Coastguard Worker except OSError: 203*cda5da8dSAndroid Build Coastguard Worker return False 204*cda5da8dSAndroid Build Coastguard Worker 205*cda5da8dSAndroid Build Coastguard Worker 206*cda5da8dSAndroid Build Coastguard Workerclass UnixBrowser(BaseBrowser): 207*cda5da8dSAndroid Build Coastguard Worker """Parent class for all Unix browsers with remote functionality.""" 208*cda5da8dSAndroid Build Coastguard Worker 209*cda5da8dSAndroid Build Coastguard Worker raise_opts = None 210*cda5da8dSAndroid Build Coastguard Worker background = False 211*cda5da8dSAndroid Build Coastguard Worker redirect_stdout = True 212*cda5da8dSAndroid Build Coastguard Worker # In remote_args, %s will be replaced with the requested URL. %action will 213*cda5da8dSAndroid Build Coastguard Worker # be replaced depending on the value of 'new' passed to open. 214*cda5da8dSAndroid Build Coastguard Worker # remote_action is used for new=0 (open). If newwin is not None, it is 215*cda5da8dSAndroid Build Coastguard Worker # used for new=1 (open_new). If newtab is not None, it is used for 216*cda5da8dSAndroid Build Coastguard Worker # new=3 (open_new_tab). After both substitutions are made, any empty 217*cda5da8dSAndroid Build Coastguard Worker # strings in the transformed remote_args list will be removed. 218*cda5da8dSAndroid Build Coastguard Worker remote_args = ['%action', '%s'] 219*cda5da8dSAndroid Build Coastguard Worker remote_action = None 220*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = None 221*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = None 222*cda5da8dSAndroid Build Coastguard Worker 223*cda5da8dSAndroid Build Coastguard Worker def _invoke(self, args, remote, autoraise, url=None): 224*cda5da8dSAndroid Build Coastguard Worker raise_opt = [] 225*cda5da8dSAndroid Build Coastguard Worker if remote and self.raise_opts: 226*cda5da8dSAndroid Build Coastguard Worker # use autoraise argument only for remote invocation 227*cda5da8dSAndroid Build Coastguard Worker autoraise = int(autoraise) 228*cda5da8dSAndroid Build Coastguard Worker opt = self.raise_opts[autoraise] 229*cda5da8dSAndroid Build Coastguard Worker if opt: raise_opt = [opt] 230*cda5da8dSAndroid Build Coastguard Worker 231*cda5da8dSAndroid Build Coastguard Worker cmdline = [self.name] + raise_opt + args 232*cda5da8dSAndroid Build Coastguard Worker 233*cda5da8dSAndroid Build Coastguard Worker if remote or self.background: 234*cda5da8dSAndroid Build Coastguard Worker inout = subprocess.DEVNULL 235*cda5da8dSAndroid Build Coastguard Worker else: 236*cda5da8dSAndroid Build Coastguard Worker # for TTY browsers, we need stdin/out 237*cda5da8dSAndroid Build Coastguard Worker inout = None 238*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(cmdline, close_fds=True, stdin=inout, 239*cda5da8dSAndroid Build Coastguard Worker stdout=(self.redirect_stdout and inout or None), 240*cda5da8dSAndroid Build Coastguard Worker stderr=inout, start_new_session=True) 241*cda5da8dSAndroid Build Coastguard Worker if remote: 242*cda5da8dSAndroid Build Coastguard Worker # wait at most five seconds. If the subprocess is not finished, the 243*cda5da8dSAndroid Build Coastguard Worker # remote invocation has (hopefully) started a new instance. 244*cda5da8dSAndroid Build Coastguard Worker try: 245*cda5da8dSAndroid Build Coastguard Worker rc = p.wait(5) 246*cda5da8dSAndroid Build Coastguard Worker # if remote call failed, open() will try direct invocation 247*cda5da8dSAndroid Build Coastguard Worker return not rc 248*cda5da8dSAndroid Build Coastguard Worker except subprocess.TimeoutExpired: 249*cda5da8dSAndroid Build Coastguard Worker return True 250*cda5da8dSAndroid Build Coastguard Worker elif self.background: 251*cda5da8dSAndroid Build Coastguard Worker if p.poll() is None: 252*cda5da8dSAndroid Build Coastguard Worker return True 253*cda5da8dSAndroid Build Coastguard Worker else: 254*cda5da8dSAndroid Build Coastguard Worker return False 255*cda5da8dSAndroid Build Coastguard Worker else: 256*cda5da8dSAndroid Build Coastguard Worker return not p.wait() 257*cda5da8dSAndroid Build Coastguard Worker 258*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 259*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 260*cda5da8dSAndroid Build Coastguard Worker if new == 0: 261*cda5da8dSAndroid Build Coastguard Worker action = self.remote_action 262*cda5da8dSAndroid Build Coastguard Worker elif new == 1: 263*cda5da8dSAndroid Build Coastguard Worker action = self.remote_action_newwin 264*cda5da8dSAndroid Build Coastguard Worker elif new == 2: 265*cda5da8dSAndroid Build Coastguard Worker if self.remote_action_newtab is None: 266*cda5da8dSAndroid Build Coastguard Worker action = self.remote_action_newwin 267*cda5da8dSAndroid Build Coastguard Worker else: 268*cda5da8dSAndroid Build Coastguard Worker action = self.remote_action_newtab 269*cda5da8dSAndroid Build Coastguard Worker else: 270*cda5da8dSAndroid Build Coastguard Worker raise Error("Bad 'new' parameter to open(); " + 271*cda5da8dSAndroid Build Coastguard Worker "expected 0, 1, or 2, got %s" % new) 272*cda5da8dSAndroid Build Coastguard Worker 273*cda5da8dSAndroid Build Coastguard Worker args = [arg.replace("%s", url).replace("%action", action) 274*cda5da8dSAndroid Build Coastguard Worker for arg in self.remote_args] 275*cda5da8dSAndroid Build Coastguard Worker args = [arg for arg in args if arg] 276*cda5da8dSAndroid Build Coastguard Worker success = self._invoke(args, True, autoraise, url) 277*cda5da8dSAndroid Build Coastguard Worker if not success: 278*cda5da8dSAndroid Build Coastguard Worker # remote invocation failed, try straight way 279*cda5da8dSAndroid Build Coastguard Worker args = [arg.replace("%s", url) for arg in self.args] 280*cda5da8dSAndroid Build Coastguard Worker return self._invoke(args, False, False) 281*cda5da8dSAndroid Build Coastguard Worker else: 282*cda5da8dSAndroid Build Coastguard Worker return True 283*cda5da8dSAndroid Build Coastguard Worker 284*cda5da8dSAndroid Build Coastguard Worker 285*cda5da8dSAndroid Build Coastguard Workerclass Mozilla(UnixBrowser): 286*cda5da8dSAndroid Build Coastguard Worker """Launcher class for Mozilla browsers.""" 287*cda5da8dSAndroid Build Coastguard Worker 288*cda5da8dSAndroid Build Coastguard Worker remote_args = ['%action', '%s'] 289*cda5da8dSAndroid Build Coastguard Worker remote_action = "" 290*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = "-new-window" 291*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = "-new-tab" 292*cda5da8dSAndroid Build Coastguard Worker background = True 293*cda5da8dSAndroid Build Coastguard Worker 294*cda5da8dSAndroid Build Coastguard Worker 295*cda5da8dSAndroid Build Coastguard Workerclass Netscape(UnixBrowser): 296*cda5da8dSAndroid Build Coastguard Worker """Launcher class for Netscape browser.""" 297*cda5da8dSAndroid Build Coastguard Worker 298*cda5da8dSAndroid Build Coastguard Worker raise_opts = ["-noraise", "-raise"] 299*cda5da8dSAndroid Build Coastguard Worker remote_args = ['-remote', 'openURL(%s%action)'] 300*cda5da8dSAndroid Build Coastguard Worker remote_action = "" 301*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = ",new-window" 302*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = ",new-tab" 303*cda5da8dSAndroid Build Coastguard Worker background = True 304*cda5da8dSAndroid Build Coastguard Worker 305*cda5da8dSAndroid Build Coastguard Worker 306*cda5da8dSAndroid Build Coastguard Workerclass Galeon(UnixBrowser): 307*cda5da8dSAndroid Build Coastguard Worker """Launcher class for Galeon/Epiphany browsers.""" 308*cda5da8dSAndroid Build Coastguard Worker 309*cda5da8dSAndroid Build Coastguard Worker raise_opts = ["-noraise", ""] 310*cda5da8dSAndroid Build Coastguard Worker remote_args = ['%action', '%s'] 311*cda5da8dSAndroid Build Coastguard Worker remote_action = "-n" 312*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = "-w" 313*cda5da8dSAndroid Build Coastguard Worker background = True 314*cda5da8dSAndroid Build Coastguard Worker 315*cda5da8dSAndroid Build Coastguard Worker 316*cda5da8dSAndroid Build Coastguard Workerclass Chrome(UnixBrowser): 317*cda5da8dSAndroid Build Coastguard Worker "Launcher class for Google Chrome browser." 318*cda5da8dSAndroid Build Coastguard Worker 319*cda5da8dSAndroid Build Coastguard Worker remote_args = ['%action', '%s'] 320*cda5da8dSAndroid Build Coastguard Worker remote_action = "" 321*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = "--new-window" 322*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = "" 323*cda5da8dSAndroid Build Coastguard Worker background = True 324*cda5da8dSAndroid Build Coastguard Worker 325*cda5da8dSAndroid Build Coastguard WorkerChromium = Chrome 326*cda5da8dSAndroid Build Coastguard Worker 327*cda5da8dSAndroid Build Coastguard Worker 328*cda5da8dSAndroid Build Coastguard Workerclass Opera(UnixBrowser): 329*cda5da8dSAndroid Build Coastguard Worker "Launcher class for Opera browser." 330*cda5da8dSAndroid Build Coastguard Worker 331*cda5da8dSAndroid Build Coastguard Worker remote_args = ['%action', '%s'] 332*cda5da8dSAndroid Build Coastguard Worker remote_action = "" 333*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = "--new-window" 334*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = "" 335*cda5da8dSAndroid Build Coastguard Worker background = True 336*cda5da8dSAndroid Build Coastguard Worker 337*cda5da8dSAndroid Build Coastguard Worker 338*cda5da8dSAndroid Build Coastguard Workerclass Elinks(UnixBrowser): 339*cda5da8dSAndroid Build Coastguard Worker "Launcher class for Elinks browsers." 340*cda5da8dSAndroid Build Coastguard Worker 341*cda5da8dSAndroid Build Coastguard Worker remote_args = ['-remote', 'openURL(%s%action)'] 342*cda5da8dSAndroid Build Coastguard Worker remote_action = "" 343*cda5da8dSAndroid Build Coastguard Worker remote_action_newwin = ",new-window" 344*cda5da8dSAndroid Build Coastguard Worker remote_action_newtab = ",new-tab" 345*cda5da8dSAndroid Build Coastguard Worker background = False 346*cda5da8dSAndroid Build Coastguard Worker 347*cda5da8dSAndroid Build Coastguard Worker # elinks doesn't like its stdout to be redirected - 348*cda5da8dSAndroid Build Coastguard Worker # it uses redirected stdout as a signal to do -dump 349*cda5da8dSAndroid Build Coastguard Worker redirect_stdout = False 350*cda5da8dSAndroid Build Coastguard Worker 351*cda5da8dSAndroid Build Coastguard Worker 352*cda5da8dSAndroid Build Coastguard Workerclass Konqueror(BaseBrowser): 353*cda5da8dSAndroid Build Coastguard Worker """Controller for the KDE File Manager (kfm, or Konqueror). 354*cda5da8dSAndroid Build Coastguard Worker 355*cda5da8dSAndroid Build Coastguard Worker See the output of ``kfmclient --commands`` 356*cda5da8dSAndroid Build Coastguard Worker for more information on the Konqueror remote-control interface. 357*cda5da8dSAndroid Build Coastguard Worker """ 358*cda5da8dSAndroid Build Coastguard Worker 359*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 360*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 361*cda5da8dSAndroid Build Coastguard Worker # XXX Currently I know no way to prevent KFM from opening a new win. 362*cda5da8dSAndroid Build Coastguard Worker if new == 2: 363*cda5da8dSAndroid Build Coastguard Worker action = "newTab" 364*cda5da8dSAndroid Build Coastguard Worker else: 365*cda5da8dSAndroid Build Coastguard Worker action = "openURL" 366*cda5da8dSAndroid Build Coastguard Worker 367*cda5da8dSAndroid Build Coastguard Worker devnull = subprocess.DEVNULL 368*cda5da8dSAndroid Build Coastguard Worker 369*cda5da8dSAndroid Build Coastguard Worker try: 370*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(["kfmclient", action, url], 371*cda5da8dSAndroid Build Coastguard Worker close_fds=True, stdin=devnull, 372*cda5da8dSAndroid Build Coastguard Worker stdout=devnull, stderr=devnull) 373*cda5da8dSAndroid Build Coastguard Worker except OSError: 374*cda5da8dSAndroid Build Coastguard Worker # fall through to next variant 375*cda5da8dSAndroid Build Coastguard Worker pass 376*cda5da8dSAndroid Build Coastguard Worker else: 377*cda5da8dSAndroid Build Coastguard Worker p.wait() 378*cda5da8dSAndroid Build Coastguard Worker # kfmclient's return code unfortunately has no meaning as it seems 379*cda5da8dSAndroid Build Coastguard Worker return True 380*cda5da8dSAndroid Build Coastguard Worker 381*cda5da8dSAndroid Build Coastguard Worker try: 382*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(["konqueror", "--silent", url], 383*cda5da8dSAndroid Build Coastguard Worker close_fds=True, stdin=devnull, 384*cda5da8dSAndroid Build Coastguard Worker stdout=devnull, stderr=devnull, 385*cda5da8dSAndroid Build Coastguard Worker start_new_session=True) 386*cda5da8dSAndroid Build Coastguard Worker except OSError: 387*cda5da8dSAndroid Build Coastguard Worker # fall through to next variant 388*cda5da8dSAndroid Build Coastguard Worker pass 389*cda5da8dSAndroid Build Coastguard Worker else: 390*cda5da8dSAndroid Build Coastguard Worker if p.poll() is None: 391*cda5da8dSAndroid Build Coastguard Worker # Should be running now. 392*cda5da8dSAndroid Build Coastguard Worker return True 393*cda5da8dSAndroid Build Coastguard Worker 394*cda5da8dSAndroid Build Coastguard Worker try: 395*cda5da8dSAndroid Build Coastguard Worker p = subprocess.Popen(["kfm", "-d", url], 396*cda5da8dSAndroid Build Coastguard Worker close_fds=True, stdin=devnull, 397*cda5da8dSAndroid Build Coastguard Worker stdout=devnull, stderr=devnull, 398*cda5da8dSAndroid Build Coastguard Worker start_new_session=True) 399*cda5da8dSAndroid Build Coastguard Worker except OSError: 400*cda5da8dSAndroid Build Coastguard Worker return False 401*cda5da8dSAndroid Build Coastguard Worker else: 402*cda5da8dSAndroid Build Coastguard Worker return (p.poll() is None) 403*cda5da8dSAndroid Build Coastguard Worker 404*cda5da8dSAndroid Build Coastguard Worker 405*cda5da8dSAndroid Build Coastguard Workerclass Grail(BaseBrowser): 406*cda5da8dSAndroid Build Coastguard Worker # There should be a way to maintain a connection to Grail, but the 407*cda5da8dSAndroid Build Coastguard Worker # Grail remote control protocol doesn't really allow that at this 408*cda5da8dSAndroid Build Coastguard Worker # point. It probably never will! 409*cda5da8dSAndroid Build Coastguard Worker def _find_grail_rc(self): 410*cda5da8dSAndroid Build Coastguard Worker import glob 411*cda5da8dSAndroid Build Coastguard Worker import pwd 412*cda5da8dSAndroid Build Coastguard Worker import socket 413*cda5da8dSAndroid Build Coastguard Worker import tempfile 414*cda5da8dSAndroid Build Coastguard Worker tempdir = os.path.join(tempfile.gettempdir(), 415*cda5da8dSAndroid Build Coastguard Worker ".grail-unix") 416*cda5da8dSAndroid Build Coastguard Worker user = pwd.getpwuid(os.getuid())[0] 417*cda5da8dSAndroid Build Coastguard Worker filename = os.path.join(glob.escape(tempdir), glob.escape(user) + "-*") 418*cda5da8dSAndroid Build Coastguard Worker maybes = glob.glob(filename) 419*cda5da8dSAndroid Build Coastguard Worker if not maybes: 420*cda5da8dSAndroid Build Coastguard Worker return None 421*cda5da8dSAndroid Build Coastguard Worker s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 422*cda5da8dSAndroid Build Coastguard Worker for fn in maybes: 423*cda5da8dSAndroid Build Coastguard Worker # need to PING each one until we find one that's live 424*cda5da8dSAndroid Build Coastguard Worker try: 425*cda5da8dSAndroid Build Coastguard Worker s.connect(fn) 426*cda5da8dSAndroid Build Coastguard Worker except OSError: 427*cda5da8dSAndroid Build Coastguard Worker # no good; attempt to clean it out, but don't fail: 428*cda5da8dSAndroid Build Coastguard Worker try: 429*cda5da8dSAndroid Build Coastguard Worker os.unlink(fn) 430*cda5da8dSAndroid Build Coastguard Worker except OSError: 431*cda5da8dSAndroid Build Coastguard Worker pass 432*cda5da8dSAndroid Build Coastguard Worker else: 433*cda5da8dSAndroid Build Coastguard Worker return s 434*cda5da8dSAndroid Build Coastguard Worker 435*cda5da8dSAndroid Build Coastguard Worker def _remote(self, action): 436*cda5da8dSAndroid Build Coastguard Worker s = self._find_grail_rc() 437*cda5da8dSAndroid Build Coastguard Worker if not s: 438*cda5da8dSAndroid Build Coastguard Worker return 0 439*cda5da8dSAndroid Build Coastguard Worker s.send(action) 440*cda5da8dSAndroid Build Coastguard Worker s.close() 441*cda5da8dSAndroid Build Coastguard Worker return 1 442*cda5da8dSAndroid Build Coastguard Worker 443*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 444*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 445*cda5da8dSAndroid Build Coastguard Worker if new: 446*cda5da8dSAndroid Build Coastguard Worker ok = self._remote("LOADNEW " + url) 447*cda5da8dSAndroid Build Coastguard Worker else: 448*cda5da8dSAndroid Build Coastguard Worker ok = self._remote("LOAD " + url) 449*cda5da8dSAndroid Build Coastguard Worker return ok 450*cda5da8dSAndroid Build Coastguard Worker 451*cda5da8dSAndroid Build Coastguard Worker 452*cda5da8dSAndroid Build Coastguard Worker# 453*cda5da8dSAndroid Build Coastguard Worker# Platform support for Unix 454*cda5da8dSAndroid Build Coastguard Worker# 455*cda5da8dSAndroid Build Coastguard Worker 456*cda5da8dSAndroid Build Coastguard Worker# These are the right tests because all these Unix browsers require either 457*cda5da8dSAndroid Build Coastguard Worker# a console terminal or an X display to run. 458*cda5da8dSAndroid Build Coastguard Worker 459*cda5da8dSAndroid Build Coastguard Workerdef register_X_browsers(): 460*cda5da8dSAndroid Build Coastguard Worker 461*cda5da8dSAndroid Build Coastguard Worker # use xdg-open if around 462*cda5da8dSAndroid Build Coastguard Worker if shutil.which("xdg-open"): 463*cda5da8dSAndroid Build Coastguard Worker register("xdg-open", None, BackgroundBrowser("xdg-open")) 464*cda5da8dSAndroid Build Coastguard Worker 465*cda5da8dSAndroid Build Coastguard Worker # Opens an appropriate browser for the URL scheme according to 466*cda5da8dSAndroid Build Coastguard Worker # freedesktop.org settings (GNOME, KDE, XFCE, etc.) 467*cda5da8dSAndroid Build Coastguard Worker if shutil.which("gio"): 468*cda5da8dSAndroid Build Coastguard Worker register("gio", None, BackgroundBrowser(["gio", "open", "--", "%s"])) 469*cda5da8dSAndroid Build Coastguard Worker 470*cda5da8dSAndroid Build Coastguard Worker # Equivalent of gio open before 2015 471*cda5da8dSAndroid Build Coastguard Worker if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"): 472*cda5da8dSAndroid Build Coastguard Worker register("gvfs-open", None, BackgroundBrowser("gvfs-open")) 473*cda5da8dSAndroid Build Coastguard Worker 474*cda5da8dSAndroid Build Coastguard Worker # The default KDE browser 475*cda5da8dSAndroid Build Coastguard Worker if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"): 476*cda5da8dSAndroid Build Coastguard Worker register("kfmclient", Konqueror, Konqueror("kfmclient")) 477*cda5da8dSAndroid Build Coastguard Worker 478*cda5da8dSAndroid Build Coastguard Worker if shutil.which("x-www-browser"): 479*cda5da8dSAndroid Build Coastguard Worker register("x-www-browser", None, BackgroundBrowser("x-www-browser")) 480*cda5da8dSAndroid Build Coastguard Worker 481*cda5da8dSAndroid Build Coastguard Worker # The Mozilla browsers 482*cda5da8dSAndroid Build Coastguard Worker for browser in ("firefox", "iceweasel", "iceape", "seamonkey"): 483*cda5da8dSAndroid Build Coastguard Worker if shutil.which(browser): 484*cda5da8dSAndroid Build Coastguard Worker register(browser, None, Mozilla(browser)) 485*cda5da8dSAndroid Build Coastguard Worker 486*cda5da8dSAndroid Build Coastguard Worker # The Netscape and old Mozilla browsers 487*cda5da8dSAndroid Build Coastguard Worker for browser in ("mozilla-firefox", 488*cda5da8dSAndroid Build Coastguard Worker "mozilla-firebird", "firebird", 489*cda5da8dSAndroid Build Coastguard Worker "mozilla", "netscape"): 490*cda5da8dSAndroid Build Coastguard Worker if shutil.which(browser): 491*cda5da8dSAndroid Build Coastguard Worker register(browser, None, Netscape(browser)) 492*cda5da8dSAndroid Build Coastguard Worker 493*cda5da8dSAndroid Build Coastguard Worker # Konqueror/kfm, the KDE browser. 494*cda5da8dSAndroid Build Coastguard Worker if shutil.which("kfm"): 495*cda5da8dSAndroid Build Coastguard Worker register("kfm", Konqueror, Konqueror("kfm")) 496*cda5da8dSAndroid Build Coastguard Worker elif shutil.which("konqueror"): 497*cda5da8dSAndroid Build Coastguard Worker register("konqueror", Konqueror, Konqueror("konqueror")) 498*cda5da8dSAndroid Build Coastguard Worker 499*cda5da8dSAndroid Build Coastguard Worker # Gnome's Galeon and Epiphany 500*cda5da8dSAndroid Build Coastguard Worker for browser in ("galeon", "epiphany"): 501*cda5da8dSAndroid Build Coastguard Worker if shutil.which(browser): 502*cda5da8dSAndroid Build Coastguard Worker register(browser, None, Galeon(browser)) 503*cda5da8dSAndroid Build Coastguard Worker 504*cda5da8dSAndroid Build Coastguard Worker # Skipstone, another Gtk/Mozilla based browser 505*cda5da8dSAndroid Build Coastguard Worker if shutil.which("skipstone"): 506*cda5da8dSAndroid Build Coastguard Worker register("skipstone", None, BackgroundBrowser("skipstone")) 507*cda5da8dSAndroid Build Coastguard Worker 508*cda5da8dSAndroid Build Coastguard Worker # Google Chrome/Chromium browsers 509*cda5da8dSAndroid Build Coastguard Worker for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"): 510*cda5da8dSAndroid Build Coastguard Worker if shutil.which(browser): 511*cda5da8dSAndroid Build Coastguard Worker register(browser, None, Chrome(browser)) 512*cda5da8dSAndroid Build Coastguard Worker 513*cda5da8dSAndroid Build Coastguard Worker # Opera, quite popular 514*cda5da8dSAndroid Build Coastguard Worker if shutil.which("opera"): 515*cda5da8dSAndroid Build Coastguard Worker register("opera", None, Opera("opera")) 516*cda5da8dSAndroid Build Coastguard Worker 517*cda5da8dSAndroid Build Coastguard Worker # Next, Mosaic -- old but still in use. 518*cda5da8dSAndroid Build Coastguard Worker if shutil.which("mosaic"): 519*cda5da8dSAndroid Build Coastguard Worker register("mosaic", None, BackgroundBrowser("mosaic")) 520*cda5da8dSAndroid Build Coastguard Worker 521*cda5da8dSAndroid Build Coastguard Worker # Grail, the Python browser. Does anybody still use it? 522*cda5da8dSAndroid Build Coastguard Worker if shutil.which("grail"): 523*cda5da8dSAndroid Build Coastguard Worker register("grail", Grail, None) 524*cda5da8dSAndroid Build Coastguard Worker 525*cda5da8dSAndroid Build Coastguard Workerdef register_standard_browsers(): 526*cda5da8dSAndroid Build Coastguard Worker global _tryorder 527*cda5da8dSAndroid Build Coastguard Worker _tryorder = [] 528*cda5da8dSAndroid Build Coastguard Worker 529*cda5da8dSAndroid Build Coastguard Worker if sys.platform == 'darwin': 530*cda5da8dSAndroid Build Coastguard Worker register("MacOSX", None, MacOSXOSAScript('default')) 531*cda5da8dSAndroid Build Coastguard Worker register("chrome", None, MacOSXOSAScript('chrome')) 532*cda5da8dSAndroid Build Coastguard Worker register("firefox", None, MacOSXOSAScript('firefox')) 533*cda5da8dSAndroid Build Coastguard Worker register("safari", None, MacOSXOSAScript('safari')) 534*cda5da8dSAndroid Build Coastguard Worker # OS X can use below Unix support (but we prefer using the OS X 535*cda5da8dSAndroid Build Coastguard Worker # specific stuff) 536*cda5da8dSAndroid Build Coastguard Worker 537*cda5da8dSAndroid Build Coastguard Worker if sys.platform == "serenityos": 538*cda5da8dSAndroid Build Coastguard Worker # SerenityOS webbrowser, simply called "Browser". 539*cda5da8dSAndroid Build Coastguard Worker register("Browser", None, BackgroundBrowser("Browser")) 540*cda5da8dSAndroid Build Coastguard Worker 541*cda5da8dSAndroid Build Coastguard Worker if sys.platform[:3] == "win": 542*cda5da8dSAndroid Build Coastguard Worker # First try to use the default Windows browser 543*cda5da8dSAndroid Build Coastguard Worker register("windows-default", WindowsDefault) 544*cda5da8dSAndroid Build Coastguard Worker 545*cda5da8dSAndroid Build Coastguard Worker # Detect some common Windows browsers, fallback to IE 546*cda5da8dSAndroid Build Coastguard Worker iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"), 547*cda5da8dSAndroid Build Coastguard Worker "Internet Explorer\\IEXPLORE.EXE") 548*cda5da8dSAndroid Build Coastguard Worker for browser in ("firefox", "firebird", "seamonkey", "mozilla", 549*cda5da8dSAndroid Build Coastguard Worker "netscape", "opera", iexplore): 550*cda5da8dSAndroid Build Coastguard Worker if shutil.which(browser): 551*cda5da8dSAndroid Build Coastguard Worker register(browser, None, BackgroundBrowser(browser)) 552*cda5da8dSAndroid Build Coastguard Worker else: 553*cda5da8dSAndroid Build Coastguard Worker # Prefer X browsers if present 554*cda5da8dSAndroid Build Coastguard Worker if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"): 555*cda5da8dSAndroid Build Coastguard Worker try: 556*cda5da8dSAndroid Build Coastguard Worker cmd = "xdg-settings get default-web-browser".split() 557*cda5da8dSAndroid Build Coastguard Worker raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL) 558*cda5da8dSAndroid Build Coastguard Worker result = raw_result.decode().strip() 559*cda5da8dSAndroid Build Coastguard Worker except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) : 560*cda5da8dSAndroid Build Coastguard Worker pass 561*cda5da8dSAndroid Build Coastguard Worker else: 562*cda5da8dSAndroid Build Coastguard Worker global _os_preferred_browser 563*cda5da8dSAndroid Build Coastguard Worker _os_preferred_browser = result 564*cda5da8dSAndroid Build Coastguard Worker 565*cda5da8dSAndroid Build Coastguard Worker register_X_browsers() 566*cda5da8dSAndroid Build Coastguard Worker 567*cda5da8dSAndroid Build Coastguard Worker # Also try console browsers 568*cda5da8dSAndroid Build Coastguard Worker if os.environ.get("TERM"): 569*cda5da8dSAndroid Build Coastguard Worker if shutil.which("www-browser"): 570*cda5da8dSAndroid Build Coastguard Worker register("www-browser", None, GenericBrowser("www-browser")) 571*cda5da8dSAndroid Build Coastguard Worker # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/> 572*cda5da8dSAndroid Build Coastguard Worker if shutil.which("links"): 573*cda5da8dSAndroid Build Coastguard Worker register("links", None, GenericBrowser("links")) 574*cda5da8dSAndroid Build Coastguard Worker if shutil.which("elinks"): 575*cda5da8dSAndroid Build Coastguard Worker register("elinks", None, Elinks("elinks")) 576*cda5da8dSAndroid Build Coastguard Worker # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/> 577*cda5da8dSAndroid Build Coastguard Worker if shutil.which("lynx"): 578*cda5da8dSAndroid Build Coastguard Worker register("lynx", None, GenericBrowser("lynx")) 579*cda5da8dSAndroid Build Coastguard Worker # The w3m browser <http://w3m.sourceforge.net/> 580*cda5da8dSAndroid Build Coastguard Worker if shutil.which("w3m"): 581*cda5da8dSAndroid Build Coastguard Worker register("w3m", None, GenericBrowser("w3m")) 582*cda5da8dSAndroid Build Coastguard Worker 583*cda5da8dSAndroid Build Coastguard Worker # OK, now that we know what the default preference orders for each 584*cda5da8dSAndroid Build Coastguard Worker # platform are, allow user to override them with the BROWSER variable. 585*cda5da8dSAndroid Build Coastguard Worker if "BROWSER" in os.environ: 586*cda5da8dSAndroid Build Coastguard Worker userchoices = os.environ["BROWSER"].split(os.pathsep) 587*cda5da8dSAndroid Build Coastguard Worker userchoices.reverse() 588*cda5da8dSAndroid Build Coastguard Worker 589*cda5da8dSAndroid Build Coastguard Worker # Treat choices in same way as if passed into get() but do register 590*cda5da8dSAndroid Build Coastguard Worker # and prepend to _tryorder 591*cda5da8dSAndroid Build Coastguard Worker for cmdline in userchoices: 592*cda5da8dSAndroid Build Coastguard Worker if cmdline != '': 593*cda5da8dSAndroid Build Coastguard Worker cmd = _synthesize(cmdline, preferred=True) 594*cda5da8dSAndroid Build Coastguard Worker if cmd[1] is None: 595*cda5da8dSAndroid Build Coastguard Worker register(cmdline, None, GenericBrowser(cmdline), preferred=True) 596*cda5da8dSAndroid Build Coastguard Worker 597*cda5da8dSAndroid Build Coastguard Worker # what to do if _tryorder is now empty? 598*cda5da8dSAndroid Build Coastguard Worker 599*cda5da8dSAndroid Build Coastguard Worker 600*cda5da8dSAndroid Build Coastguard Worker# 601*cda5da8dSAndroid Build Coastguard Worker# Platform support for Windows 602*cda5da8dSAndroid Build Coastguard Worker# 603*cda5da8dSAndroid Build Coastguard Worker 604*cda5da8dSAndroid Build Coastguard Workerif sys.platform[:3] == "win": 605*cda5da8dSAndroid Build Coastguard Worker class WindowsDefault(BaseBrowser): 606*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 607*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 608*cda5da8dSAndroid Build Coastguard Worker try: 609*cda5da8dSAndroid Build Coastguard Worker os.startfile(url) 610*cda5da8dSAndroid Build Coastguard Worker except OSError: 611*cda5da8dSAndroid Build Coastguard Worker # [Error 22] No application is associated with the specified 612*cda5da8dSAndroid Build Coastguard Worker # file for this operation: '<URL>' 613*cda5da8dSAndroid Build Coastguard Worker return False 614*cda5da8dSAndroid Build Coastguard Worker else: 615*cda5da8dSAndroid Build Coastguard Worker return True 616*cda5da8dSAndroid Build Coastguard Worker 617*cda5da8dSAndroid Build Coastguard Worker# 618*cda5da8dSAndroid Build Coastguard Worker# Platform support for MacOS 619*cda5da8dSAndroid Build Coastguard Worker# 620*cda5da8dSAndroid Build Coastguard Worker 621*cda5da8dSAndroid Build Coastguard Workerif sys.platform == 'darwin': 622*cda5da8dSAndroid Build Coastguard Worker # Adapted from patch submitted to SourceForge by Steven J. Burr 623*cda5da8dSAndroid Build Coastguard Worker class MacOSX(BaseBrowser): 624*cda5da8dSAndroid Build Coastguard Worker """Launcher class for Aqua browsers on Mac OS X 625*cda5da8dSAndroid Build Coastguard Worker 626*cda5da8dSAndroid Build Coastguard Worker Optionally specify a browser name on instantiation. Note that this 627*cda5da8dSAndroid Build Coastguard Worker will not work for Aqua browsers if the user has moved the application 628*cda5da8dSAndroid Build Coastguard Worker package after installation. 629*cda5da8dSAndroid Build Coastguard Worker 630*cda5da8dSAndroid Build Coastguard Worker If no browser is specified, the default browser, as specified in the 631*cda5da8dSAndroid Build Coastguard Worker Internet System Preferences panel, will be used. 632*cda5da8dSAndroid Build Coastguard Worker """ 633*cda5da8dSAndroid Build Coastguard Worker def __init__(self, name): 634*cda5da8dSAndroid Build Coastguard Worker warnings.warn(f'{self.__class__.__name__} is deprecated in 3.11' 635*cda5da8dSAndroid Build Coastguard Worker ' use MacOSXOSAScript instead.', DeprecationWarning, stacklevel=2) 636*cda5da8dSAndroid Build Coastguard Worker self.name = name 637*cda5da8dSAndroid Build Coastguard Worker 638*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 639*cda5da8dSAndroid Build Coastguard Worker sys.audit("webbrowser.open", url) 640*cda5da8dSAndroid Build Coastguard Worker assert "'" not in url 641*cda5da8dSAndroid Build Coastguard Worker # hack for local urls 642*cda5da8dSAndroid Build Coastguard Worker if not ':' in url: 643*cda5da8dSAndroid Build Coastguard Worker url = 'file:'+url 644*cda5da8dSAndroid Build Coastguard Worker 645*cda5da8dSAndroid Build Coastguard Worker # new must be 0 or 1 646*cda5da8dSAndroid Build Coastguard Worker new = int(bool(new)) 647*cda5da8dSAndroid Build Coastguard Worker if self.name == "default": 648*cda5da8dSAndroid Build Coastguard Worker # User called open, open_new or get without a browser parameter 649*cda5da8dSAndroid Build Coastguard Worker script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser 650*cda5da8dSAndroid Build Coastguard Worker else: 651*cda5da8dSAndroid Build Coastguard Worker # User called get and chose a browser 652*cda5da8dSAndroid Build Coastguard Worker if self.name == "OmniWeb": 653*cda5da8dSAndroid Build Coastguard Worker toWindow = "" 654*cda5da8dSAndroid Build Coastguard Worker else: 655*cda5da8dSAndroid Build Coastguard Worker # Include toWindow parameter of OpenURL command for browsers 656*cda5da8dSAndroid Build Coastguard Worker # that support it. 0 == new window; -1 == existing 657*cda5da8dSAndroid Build Coastguard Worker toWindow = "toWindow %d" % (new - 1) 658*cda5da8dSAndroid Build Coastguard Worker cmd = 'OpenURL "%s"' % url.replace('"', '%22') 659*cda5da8dSAndroid Build Coastguard Worker script = '''tell application "%s" 660*cda5da8dSAndroid Build Coastguard Worker activate 661*cda5da8dSAndroid Build Coastguard Worker %s %s 662*cda5da8dSAndroid Build Coastguard Worker end tell''' % (self.name, cmd, toWindow) 663*cda5da8dSAndroid Build Coastguard Worker # Open pipe to AppleScript through osascript command 664*cda5da8dSAndroid Build Coastguard Worker osapipe = os.popen("osascript", "w") 665*cda5da8dSAndroid Build Coastguard Worker if osapipe is None: 666*cda5da8dSAndroid Build Coastguard Worker return False 667*cda5da8dSAndroid Build Coastguard Worker # Write script to osascript's stdin 668*cda5da8dSAndroid Build Coastguard Worker osapipe.write(script) 669*cda5da8dSAndroid Build Coastguard Worker rc = osapipe.close() 670*cda5da8dSAndroid Build Coastguard Worker return not rc 671*cda5da8dSAndroid Build Coastguard Worker 672*cda5da8dSAndroid Build Coastguard Worker class MacOSXOSAScript(BaseBrowser): 673*cda5da8dSAndroid Build Coastguard Worker def __init__(self, name='default'): 674*cda5da8dSAndroid Build Coastguard Worker super().__init__(name) 675*cda5da8dSAndroid Build Coastguard Worker 676*cda5da8dSAndroid Build Coastguard Worker @property 677*cda5da8dSAndroid Build Coastguard Worker def _name(self): 678*cda5da8dSAndroid Build Coastguard Worker warnings.warn(f'{self.__class__.__name__}._name is deprecated in 3.11' 679*cda5da8dSAndroid Build Coastguard Worker f' use {self.__class__.__name__}.name instead.', 680*cda5da8dSAndroid Build Coastguard Worker DeprecationWarning, stacklevel=2) 681*cda5da8dSAndroid Build Coastguard Worker return self.name 682*cda5da8dSAndroid Build Coastguard Worker 683*cda5da8dSAndroid Build Coastguard Worker @_name.setter 684*cda5da8dSAndroid Build Coastguard Worker def _name(self, val): 685*cda5da8dSAndroid Build Coastguard Worker warnings.warn(f'{self.__class__.__name__}._name is deprecated in 3.11' 686*cda5da8dSAndroid Build Coastguard Worker f' use {self.__class__.__name__}.name instead.', 687*cda5da8dSAndroid Build Coastguard Worker DeprecationWarning, stacklevel=2) 688*cda5da8dSAndroid Build Coastguard Worker self.name = val 689*cda5da8dSAndroid Build Coastguard Worker 690*cda5da8dSAndroid Build Coastguard Worker def open(self, url, new=0, autoraise=True): 691*cda5da8dSAndroid Build Coastguard Worker if self.name == 'default': 692*cda5da8dSAndroid Build Coastguard Worker script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser 693*cda5da8dSAndroid Build Coastguard Worker else: 694*cda5da8dSAndroid Build Coastguard Worker script = f''' 695*cda5da8dSAndroid Build Coastguard Worker tell application "%s" 696*cda5da8dSAndroid Build Coastguard Worker activate 697*cda5da8dSAndroid Build Coastguard Worker open location "%s" 698*cda5da8dSAndroid Build Coastguard Worker end 699*cda5da8dSAndroid Build Coastguard Worker '''%(self.name, url.replace('"', '%22')) 700*cda5da8dSAndroid Build Coastguard Worker 701*cda5da8dSAndroid Build Coastguard Worker osapipe = os.popen("osascript", "w") 702*cda5da8dSAndroid Build Coastguard Worker if osapipe is None: 703*cda5da8dSAndroid Build Coastguard Worker return False 704*cda5da8dSAndroid Build Coastguard Worker 705*cda5da8dSAndroid Build Coastguard Worker osapipe.write(script) 706*cda5da8dSAndroid Build Coastguard Worker rc = osapipe.close() 707*cda5da8dSAndroid Build Coastguard Worker return not rc 708*cda5da8dSAndroid Build Coastguard Worker 709*cda5da8dSAndroid Build Coastguard Worker 710*cda5da8dSAndroid Build Coastguard Workerdef main(): 711*cda5da8dSAndroid Build Coastguard Worker import getopt 712*cda5da8dSAndroid Build Coastguard Worker usage = """Usage: %s [-n | -t] url 713*cda5da8dSAndroid Build Coastguard Worker -n: open new window 714*cda5da8dSAndroid Build Coastguard Worker -t: open new tab""" % sys.argv[0] 715*cda5da8dSAndroid Build Coastguard Worker try: 716*cda5da8dSAndroid Build Coastguard Worker opts, args = getopt.getopt(sys.argv[1:], 'ntd') 717*cda5da8dSAndroid Build Coastguard Worker except getopt.error as msg: 718*cda5da8dSAndroid Build Coastguard Worker print(msg, file=sys.stderr) 719*cda5da8dSAndroid Build Coastguard Worker print(usage, file=sys.stderr) 720*cda5da8dSAndroid Build Coastguard Worker sys.exit(1) 721*cda5da8dSAndroid Build Coastguard Worker new_win = 0 722*cda5da8dSAndroid Build Coastguard Worker for o, a in opts: 723*cda5da8dSAndroid Build Coastguard Worker if o == '-n': new_win = 1 724*cda5da8dSAndroid Build Coastguard Worker elif o == '-t': new_win = 2 725*cda5da8dSAndroid Build Coastguard Worker if len(args) != 1: 726*cda5da8dSAndroid Build Coastguard Worker print(usage, file=sys.stderr) 727*cda5da8dSAndroid Build Coastguard Worker sys.exit(1) 728*cda5da8dSAndroid Build Coastguard Worker 729*cda5da8dSAndroid Build Coastguard Worker url = args[0] 730*cda5da8dSAndroid Build Coastguard Worker open(url, new_win) 731*cda5da8dSAndroid Build Coastguard Worker 732*cda5da8dSAndroid Build Coastguard Worker print("\a") 733*cda5da8dSAndroid Build Coastguard Worker 734*cda5da8dSAndroid Build Coastguard Workerif __name__ == "__main__": 735*cda5da8dSAndroid Build Coastguard Worker main() 736