190f5ba538bf40bcf4fd41049c7bf4296d3ffc9c7Benjamin Peterson#! /usr/bin/env python3
20a8c29be4b5314d4bf72cb391ab078f7483cf3f7Ka-Ping Yee"""Interfaces for launching and remotely controlling Web browsers."""
3992d4a3e6e67a05b85350820157028a61d1f22cfGuido van Rossum# Maintained by Georg Brandl.
4c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
5c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drakeimport os
6d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossumimport shlex
7540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchakaimport shutil
8c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drakeimport sys
923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandlimport subprocess
10c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
11e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
1240fc16059f04ee8fda0b5956cc4883eb21ca8f8cSkip Montanaro
13c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drakeclass Error(Exception):
14c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake    pass
15c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
16658cba6706eb4a2ad8b3e235cf0db9fe1c8e9e6bTim Peters_browsers = {}          # Dictionary of available browser controllers
17658cba6706eb4a2ad8b3e235cf0db9fe1c8e9e6bTim Peters_tryorder = []          # Preference order of available browsers
18c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
19e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandldef register(name, klass, instance=None, update_tryorder=1):
20c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake    """Register a browser connector and, optionally, connection."""
21c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake    _browsers[name.lower()] = [klass, instance]
22e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    if update_tryorder > 0:
23e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        _tryorder.append(name)
24e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    elif update_tryorder < 0:
25e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        _tryorder.insert(0, name)
26c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
27f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymonddef get(using=None):
28f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond    """Return a browser launcher instance appropriate for the environment."""
2910ff706e2788a7c7ef9f8ea0108a5ede625fedadRaymond Hettinger    if using is not None:
30f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond        alternatives = [using]
31f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond    else:
32f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond        alternatives = _tryorder
33f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond    for browser in alternatives:
34bac788a3cd348203a5fdabba52e5faf65bf35c5eRaymond Hettinger        if '%s' in browser:
3523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # User gave us a command line, split it into name and args
36d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum            browser = shlex.split(browser)
37d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum            if browser[-1] == '&':
38d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                return BackgroundBrowser(browser[:-1])
39d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum            else:
40d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                return GenericBrowser(browser)
41f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond        else:
42e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            # User gave us a browser name or path.
43f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake            try:
44f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake                command = _browsers[browser.lower()]
45f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake            except KeyError:
46f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake                command = _synthesize(browser)
47e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            if command[1] is not None:
48f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond                return command[1]
49e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            elif command[0] is not None:
50e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                return command[0]()
51f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond    raise Error("could not locate runnable browser")
52c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
53c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake# Please note: the following definition hides a builtin function.
54e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
55e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# instead of "from webbrowser import *".
56c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
57e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalottidef open(url, new=0, autoraise=True):
58e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    for name in _tryorder:
59e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        browser = get(name)
60e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        if browser.open(url, new, autoraise):
61e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            return True
62e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    return False
63c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
643f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drakedef open_new(url):
65e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    return open(url, 1)
66c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
67e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandldef open_new_tab(url):
68e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    return open(url, 2)
69f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake
70e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
71e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandldef _synthesize(browser, update_tryorder=1):
72f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    """Attempt to synthesize a controller base on existing controllers.
73f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake
74f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    This is useful to create a controller when a user specifies a path to
75f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    an entry in the BROWSER environment variable -- we can copy a general
76f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    controller to operate using a specific installation of the desired
77f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    browser in this way.
78f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake
79f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    If we can't create a controller in this way, or if there is no
80f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    executable for the requested browser, return [None, None].
81f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake
82f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    """
83e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    cmd = browser.split()[0]
84540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if not shutil.which(cmd):
85f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        return [None, None]
86e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    name = os.path.basename(cmd)
87f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    try:
88f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        command = _browsers[name.lower()]
89f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    except KeyError:
90f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        return [None, None]
91f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    # now attempt to clone to fit the new name:
92f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    controller = command[1]
93f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake    if controller and name.lower() == controller.basename:
94f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        import copy
95f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        controller = copy.copy(controller)
96f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        controller.name = browser
97f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        controller.basename = os.path.basename(browser)
98e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        register(browser, None, controller, update_tryorder)
99f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake        return [None, controller]
100118aa5337c745dece455f4782799c83b9e3a7d4fAndrew M. Kuchling    return [None, None]
101f4e5bd9df540aa5b24e47d03dbb798ed277793f7Fred Drake
1023f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
103e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# General parent classes
104e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
105e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass BaseBrowser(object):
10623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    """Parent class for all browsers. Do not use directly."""
107887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
10823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    args = ['%s']
109887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
110e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    def __init__(self, name=""):
111e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        self.name = name
112b980113a8df699797b837f103ac6d2049a214551Georg Brandl        self.basename = name
113536cf99536bce562cfcb44a856fac1c84b9de4c3Tim Peters
114e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
115196f733d935bd51f8674c9761420ce990694f33aNeal Norwitz        raise NotImplementedError
116196f733d935bd51f8674c9761420ce990694f33aNeal Norwitz
117e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    def open_new(self, url):
118e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        return self.open(url, 1)
119e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
120e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    def open_new_tab(self, url):
121e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        return self.open(url, 2)
122e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
1233f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
124e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass GenericBrowser(BaseBrowser):
125e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    """Class for all browsers started with a command
126e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl       and without remote functionality."""
1273f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
12823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    def __init__(self, name):
1293172c5d263eeffff1e89d03d79be3ccc1d60fbdeGuido van Rossum        if isinstance(name, str):
13023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            self.name = name
131992d4a3e6e67a05b85350820157028a61d1f22cfGuido van Rossum            self.args = ["%s"]
13223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
13323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # name should be a list with arguments
13423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            self.name = name[0]
13523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            self.args = name[1:]
136b980113a8df699797b837f103ac6d2049a214551Georg Brandl        self.basename = os.path.basename(self.name)
1373f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
138e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
139887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters        cmdline = [self.name] + [arg.replace("%s", url)
14023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 for arg in self.args]
14123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        try:
14289f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters            if sys.platform[:3] == 'win':
14389f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters                p = subprocess.Popen(cmdline)
14489f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters            else:
14589f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters                p = subprocess.Popen(cmdline, close_fds=True)
14623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return not p.wait()
14723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        except OSError:
14823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return False
14923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
15023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
15123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandlclass BackgroundBrowser(GenericBrowser):
15223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    """Class for all browsers which are to be started in the
15323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl       background."""
15423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
155e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
15623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        cmdline = [self.name] + [arg.replace("%s", url)
15723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 for arg in self.args]
15823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        try:
15989f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters            if sys.platform[:3] == 'win':
16089f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters                p = subprocess.Popen(cmdline)
16189f507fe8c497b3f70fdcecce8bc240f9af2bbe2Thomas Wouters            else:
162feac3980ced0d80dc21982d87658876644c60e72Gregory P. Smith                p = subprocess.Popen(cmdline, close_fds=True,
163feac3980ced0d80dc21982d87658876644c60e72Gregory P. Smith                                     start_new_session=True)
16423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return (p.poll() is None)
16523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        except OSError:
16623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return False
1673f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
1683f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
169e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass UnixBrowser(BaseBrowser):
170e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    """Parent class for all Unix browsers with remote functionality."""
1713f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
172e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    raise_opts = None
17394dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    background = False
17494dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    redirect_stdout = True
17594dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # In remote_args, %s will be replaced with the requested URL.  %action will
17694dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # be replaced depending on the value of 'new' passed to open.
17794dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # remote_action is used for new=0 (open).  If newwin is not None, it is
17894dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # used for new=1 (open_new).  If newtab is not None, it is used for
17994dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # new=3 (open_new_tab).  After both substitutions are made, any empty
18094dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray    # strings in the transformed remote_args list will be removed.
18123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_args = ['%action', '%s']
182e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    remote_action = None
183e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    remote_action_newwin = None
184e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    remote_action_newtab = None
18523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
18623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    def _invoke(self, args, remote, autoraise):
18723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        raise_opt = []
18823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        if remote and self.raise_opts:
18923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # use autoraise argument only for remote invocation
190e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti            autoraise = int(autoraise)
19123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            opt = self.raise_opts[autoraise]
19223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            if opt: raise_opt = [opt]
19323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
19423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        cmdline = [self.name] + raise_opt + args
195887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
19623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        if remote or self.background:
19702ca144b92eef8d2b8da10c7b70b4e9d42480b9bR David Murray            inout = subprocess.DEVNULL
19823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
19923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # for TTY browsers, we need stdin/out
20023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            inout = None
20123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
20223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                             stdout=(self.redirect_stdout and inout or None),
2038f7724f9a4ec57d689a436a064e2e047b2ad0d97Gregory P. Smith                             stderr=inout, start_new_session=True)
20423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        if remote:
205c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea            # wait at most five seconds. If the subprocess is not finished, the
20623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # remote invocation has (hopefully) started a new instance.
207c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea            try:
208c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea                rc = p.wait(5)
209c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea                # if remote call failed, open() will try direct invocation
210c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea                return not rc
211c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea            except subprocess.TimeoutExpired:
212c9aa321a016343187e76fdae13d3a4c75e75afdeJesus Cea                return True
21323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        elif self.background:
21423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            if p.poll() is None:
21523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                return True
21623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            else:
21723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                return False
21823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
21923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return not p.wait()
2203f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
221e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
222e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        if new == 0:
223e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            action = self.remote_action
224e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        elif new == 1:
225e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            action = self.remote_action_newwin
226e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        elif new == 2:
227e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            if self.remote_action_newtab is None:
228e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                action = self.remote_action_newwin
229e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            else:
230e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                action = self.remote_action_newtab
2313f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        else:
23223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            raise Error("Bad 'new' parameter to open(); " +
23323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                        "expected 0, 1, or 2, got %s" % new)
234887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
23523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        args = [arg.replace("%s", url).replace("%action", action)
23623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                for arg in self.remote_args]
23794dd7cb0c71dd9e43bd79df2281a8baa4f3f76caR David Murray        args = [arg for arg in args if arg]
23823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        success = self._invoke(args, True, autoraise)
23923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        if not success:
24023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # remote invocation failed, try straight way
24123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            args = [arg.replace("%s", url) for arg in self.args]
24223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return self._invoke(args, False, False)
24323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
24423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return True
2453f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
2463f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
247e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass Mozilla(UnixBrowser):
248c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    """Launcher class for Mozilla browsers."""
249c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka
250c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    remote_args = ['%action', '%s']
251c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    remote_action = ""
252c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    remote_action_newwin = "-new-window"
253c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    remote_action_newtab = "-new-tab"
254c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    background = True
255c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka
256c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka
257c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchakaclass Netscape(UnixBrowser):
258c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    """Launcher class for Netscape browser."""
2593f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
26023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    raise_opts = ["-noraise", "-raise"]
26123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_args = ['-remote', 'openURL(%s%action)']
26223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action = ""
26323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newwin = ",new-window"
26423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newtab = ",new-tab"
26523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    background = True
2668dd28eb973a1c072448d961a6fb6a8a12cc3c950Neal Norwitz
2678dd28eb973a1c072448d961a6fb6a8a12cc3c950Neal Norwitz
268e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass Galeon(UnixBrowser):
269e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    """Launcher class for Galeon/Epiphany browsers."""
270e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
27123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    raise_opts = ["-noraise", ""]
27223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_args = ['%action', '%s']
27323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action = "-n"
27423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newwin = "-w"
27523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    background = True
276e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
277e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
278ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaranclass Chrome(UnixBrowser):
279ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    "Launcher class for Google Chrome browser."
280ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran
281ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    remote_args = ['%action', '%s']
282ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    remote_action = ""
283ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    remote_action_newwin = "--new-window"
284ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    remote_action_newtab = ""
285ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    background = True
286ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran
287ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil KumaranChromium = Chrome
288ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran
289ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran
29023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandlclass Opera(UnixBrowser):
29123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    "Launcher class for Opera browser."
2923f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
293dad532f7def9917707d9705fcd9201413e42ef75Terry Reedy    raise_opts = ["-noraise", ""]
29423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_args = ['-remote', 'openURL(%s%action)']
29523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action = ""
29623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newwin = ",new-window"
29723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newtab = ",new-page"
29823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    background = True
2993f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
300e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
30123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandlclass Elinks(UnixBrowser):
30223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    "Launcher class for Elinks browsers."
303e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
30423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_args = ['-remote', 'openURL(%s%action)']
30523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action = ""
30623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newwin = ",new-window"
30723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    remote_action_newtab = ",new-tab"
30823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    background = False
3093f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
31023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    # elinks doesn't like its stdout to be redirected -
31123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    # it uses redirected stdout as a signal to do -dump
31223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    redirect_stdout = False
3133f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
3143f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
31523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandlclass Konqueror(BaseBrowser):
31623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    """Controller for the KDE File Manager (kfm, or Konqueror).
317e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
31823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    See the output of ``kfmclient --commands``
31923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    for more information on the Konqueror remote-control interface.
32023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    """
321e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
322e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
32323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        # XXX Currently I know no way to prevent KFM from opening a new win.
32423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        if new == 2:
32523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            action = "newTab"
32623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
32723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            action = "openURL"
328887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
32902ca144b92eef8d2b8da10c7b70b4e9d42480b9bR David Murray        devnull = subprocess.DEVNULL
330887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
33123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        try:
33223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            p = subprocess.Popen(["kfmclient", action, url],
33323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 close_fds=True, stdin=devnull,
33423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 stdout=devnull, stderr=devnull)
33523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        except OSError:
33623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # fall through to next variant
33723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            pass
33823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
33923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            p.wait()
34023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # kfmclient's return code unfortunately has no meaning as it seems
34123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return True
342e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
34323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        try:
34423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            p = subprocess.Popen(["konqueror", "--silent", url],
34523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 close_fds=True, stdin=devnull,
34623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 stdout=devnull, stderr=devnull,
347feac3980ced0d80dc21982d87658876644c60e72Gregory P. Smith                                 start_new_session=True)
34823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        except OSError:
34923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # fall through to next variant
35023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            pass
35123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
35223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            if p.poll() is None:
35323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                # Should be running now.
35423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                return True
355887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
35623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        try:
35723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            p = subprocess.Popen(["kfm", "-d", url],
35823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 close_fds=True, stdin=devnull,
35923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                                 stdout=devnull, stderr=devnull,
360feac3980ced0d80dc21982d87658876644c60e72Gregory P. Smith                                 start_new_session=True)
36123929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        except OSError:
36223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return False
36323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        else:
36423929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            return (p.poll() is None)
365e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
366e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
367e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlclass Grail(BaseBrowser):
3683f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake    # There should be a way to maintain a connection to Grail, but the
3693f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake    # Grail remote control protocol doesn't really allow that at this
37023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl    # point.  It probably never will!
3713f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake    def _find_grail_rc(self):
3723f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        import glob
3733f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        import pwd
3743f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        import socket
3753f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        import tempfile
3763f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        tempdir = os.path.join(tempfile.gettempdir(),
3773f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                               ".grail-unix")
37816623fe3e606efdedead8ccccaf7ef583e1be97fFred Drake        user = pwd.getpwuid(os.getuid())[0]
3793f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        filename = os.path.join(tempdir, user + "-*")
3803f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        maybes = glob.glob(filename)
3813f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        if not maybes:
3823f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake            return None
3833f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
3843f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        for fn in maybes:
3853f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake            # need to PING each one until we find one that's live
3863f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake            try:
3873f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                s.connect(fn)
3880832af6628ca5ac02d0226899725297dd508470bAndrew Svetlov            except OSError:
3893f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                # no good; attempt to clean it out, but don't fail:
3903f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                try:
3913f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                    os.unlink(fn)
392f7a17b48d748e1835bcf9df86fb7fb318bb020f8Andrew Svetlov                except OSError:
3933f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                    pass
3943f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake            else:
3953f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake                return s
3963f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
3973f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake    def _remote(self, action):
3983f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        s = self._find_grail_rc()
3993f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        if not s:
4003f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake            return 0
4013f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        s.send(action)
4023f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        s.close()
4033f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        return 1
4043f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
405e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti    def open(self, url, new=0, autoraise=True):
4063f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        if new:
407e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            ok = self._remote("LOADNEW " + url)
4083f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake        else:
409e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            ok = self._remote("LOAD " + url)
410e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        return ok
4113f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
412c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
413658cba6706eb4a2ad8b3e235cf0db9fe1c8e9e6bTim Peters#
414f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond# Platform support for Unix
415f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond#
416c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
417e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# These are the right tests because all these Unix browsers require either
418e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# a console terminal or an X display to run.
419e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
420196f733d935bd51f8674c9761420ce990694f33aNeal Norwitzdef register_X_browsers():
421d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum
422da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose    # use xdg-open if around
423540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("xdg-open"):
424da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose        register("xdg-open", None, BackgroundBrowser("xdg-open"))
425da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose
426da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose    # The default GNOME3 browser
427540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
428da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose        register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
429da80b1eb2fa5db8a388fa202e6aada686d243842Matthias Klose
430d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum    # The default GNOME browser
431540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
432d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum        register("gnome-open", None, BackgroundBrowser("gnome-open"))
433d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum
434d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum    # The default KDE browser
435540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
436d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum        register("kfmclient", Konqueror, Konqueror("kfmclient"))
437d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum
438f85aca8d1eb7bf15540be3515ab249a0ab335a4cdoko@ubuntu.com    if shutil.which("x-www-browser"):
439945c3bbf42bc133d4b21265526254ff2f4e4466bdoko@ubuntu.com        register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
440945c3bbf42bc133d4b21265526254ff2f4e4466bdoko@ubuntu.com
441c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    # The Mozilla browsers
442c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
443540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka        if shutil.which(browser):
4444a5a91838b7318cc89a2ff2018b26940a5960a29Georg Brandl            register(browser, None, Mozilla(browser))
4454a5a91838b7318cc89a2ff2018b26940a5960a29Georg Brandl
446c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    # The Netscape and old Mozilla browsers
447c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka    for browser in ("mozilla-firefox",
448c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka                    "mozilla-firebird", "firebird",
449c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka                    "mozilla", "netscape"):
450c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka        if shutil.which(browser):
451c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka            register(browser, None, Netscape(browser))
452c9b750d249136b88125c28cd139c4bc694c18003Serhiy Storchaka
453e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Konqueror/kfm, the KDE browser.
454540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("kfm"):
455b980113a8df699797b837f103ac6d2049a214551Georg Brandl        register("kfm", Konqueror, Konqueror("kfm"))
456540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    elif shutil.which("konqueror"):
457b980113a8df699797b837f103ac6d2049a214551Georg Brandl        register("konqueror", Konqueror, Konqueror("konqueror"))
458e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
459e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Gnome's Galeon and Epiphany
460e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    for browser in ("galeon", "epiphany"):
461540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka        if shutil.which(browser):
462e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            register(browser, None, Galeon(browser))
463e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
464e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Skipstone, another Gtk/Mozilla based browser
465540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("skipstone"):
46623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        register("skipstone", None, BackgroundBrowser("skipstone"))
467e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
468ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    # Google Chrome/Chromium browsers
469ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran    for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
470540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka        if shutil.which(browser):
471ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran            register(browser, None, Chrome(browser))
472ea6b4187cf78ce1ac260c1620b1d4c0fd069bebaSenthil Kumaran
473e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Opera, quite popular
474540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("opera"):
475e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        register("opera", None, Opera("opera"))
476e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
477e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Next, Mosaic -- old but still in use.
478540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("mosaic"):
47923929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        register("mosaic", None, BackgroundBrowser("mosaic"))
480e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
481e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Grail, the Python browser. Does anybody still use it?
482540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("grail"):
483e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        register("grail", Grail, None)
484e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
485196f733d935bd51f8674c9761420ce990694f33aNeal Norwitz# Prefer X browsers if present
486196f733d935bd51f8674c9761420ce990694f33aNeal Norwitzif os.environ.get("DISPLAY"):
487196f733d935bd51f8674c9761420ce990694f33aNeal Norwitz    register_X_browsers()
488196f733d935bd51f8674c9761420ce990694f33aNeal Norwitz
489e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl# Also try console browsers
490e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlif os.environ.get("TERM"):
491f85aca8d1eb7bf15540be3515ab249a0ab335a4cdoko@ubuntu.com    if shutil.which("www-browser"):
492945c3bbf42bc133d4b21265526254ff2f4e4466bdoko@ubuntu.com        register("www-browser", None, GenericBrowser("www-browser"))
493e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
494540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("links"):
49523929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        register("links", None, GenericBrowser("links"))
496540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("elinks"):
497e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        register("elinks", None, Elinks("elinks"))
498e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
499540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("lynx"):
50023929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        register("lynx", None, GenericBrowser("lynx"))
501e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # The w3m browser <http://w3m.sourceforge.net/>
502540dcba28beaac416dcac22a155770be8d33a5a3Serhiy Storchaka    if shutil.which("w3m"):
50323929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl        register("w3m", None, GenericBrowser("w3m"))
5043f8f1643c8418a3e1980138c6fdb218e5be8c186Fred Drake
505f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond#
506f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond# Platform support for Windows
507f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond#
508c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
509f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymondif sys.platform[:3] == "win":
510e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    class WindowsDefault(BaseBrowser):
511e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti        def open(self, url, new=0, autoraise=True):
512d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum            try:
5132ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower                os.startfile(url)
5142606a6f197a49f04611cb5cb0d67404d1ab14481Andrew Svetlov            except OSError:
515d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                # [Error 22] No application is associated with the specified
516d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                # file for this operation: '<URL>'
517d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                return False
518d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum            else:
519d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum                return True
520e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
521e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    _tryorder = []
522e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    _browsers = {}
523d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum
5242ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower    # First try to use the default Windows browser
525d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum    register("windows-default", WindowsDefault)
526d8faa3654c2887eaa146dcdb553a9f9793bd2e5aGuido van Rossum
5272ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower    # Detect some common Windows browsers, fallback to IE
5282ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower    iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
5292ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower                            "Internet Explorer\\IEXPLORE.EXE")
5302ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower    for browser in ("firefox", "firebird", "seamonkey", "mozilla",
5312ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower                    "netscape", "opera", iexplore):
5322ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower        if shutil.which(browser):
5332ebd8f5194257d91b076e6866573b8f20f8f40a0Steve Dower            register(browser, None, BackgroundBrowser(browser))
534c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
535c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake#
536f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond# Platform support for MacOS
537f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond#
538c70b4483d2c5042c68198dc7c4945ef3cfc95b27Fred Drake
539e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlif sys.platform == 'darwin':
540e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Adapted from patch submitted to SourceForge by Steven J. Burr
541e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    class MacOSX(BaseBrowser):
542e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        """Launcher class for Aqua browsers on Mac OS X
543e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
544e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        Optionally specify a browser name on instantiation.  Note that this
545e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        will not work for Aqua browsers if the user has moved the application
546e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        package after installation.
547e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
548e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        If no browser is specified, the default browser, as specified in the
549e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        Internet System Preferences panel, will be used.
550e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        """
551e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        def __init__(self, name):
552e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            self.name = name
553e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
554e223eb8477c2d3241641a8a3e266a28d6dcd0c0eAlexandre Vassalotti        def open(self, url, new=0, autoraise=True):
555e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            assert "'" not in url
55623929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            # hack for local urls
55723929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl            if not ':' in url:
55823929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl                url = 'file:'+url
559887c080a800215250a9ebdcc589becc0bf6cac0dTim Peters
560e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            # new must be 0 or 1
561e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            new = int(bool(new))
562e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            if self.name == "default":
563e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                # User called open, open_new or get without a browser parameter
5641cb179e93fb0f698fdb5f215b3864c578d910d9aGeorg Brandl                script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
565e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            else:
566e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                # User called get and chose a browser
567e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                if self.name == "OmniWeb":
568e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                    toWindow = ""
569e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                else:
570e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                    # Include toWindow parameter of OpenURL command for browsers
571e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                    # that support it.  0 == new window; -1 == existing
572e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                    toWindow = "toWindow %d" % (new - 1)
5731cb179e93fb0f698fdb5f215b3864c578d910d9aGeorg Brandl                cmd = 'OpenURL "%s"' % url.replace('"', '%22')
574e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                script = '''tell application "%s"
575e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                                activate
576e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                                %s %s
577e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                            end tell''' % (self.name, cmd, toWindow)
578e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            # Open pipe to AppleScript through osascript command
579e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            osapipe = os.popen("osascript", "w")
580e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            if osapipe is None:
581e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl                return False
582e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            # Write script to osascript's stdin
583e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            osapipe.write(script)
584e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            rc = osapipe.close()
585e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl            return not rc
586e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
5874d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren    class MacOSXOSAScript(BaseBrowser):
5884d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren        def __init__(self, name):
5894d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            self._name = name
5904d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren
5914d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren        def open(self, url, new=0, autoraise=True):
5924d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            if self._name == 'default':
5934d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
5944d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            else:
5954d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                script = '''
5964d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                   tell application "%s"
5974d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                       activate
5984d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                       open location "%s"
5994d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                   end
6004d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                   '''%(self._name, url.replace('"', '%22'))
6014d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren
6024d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            osapipe = os.popen("osascript", "w")
6034d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            if osapipe is None:
6044d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren                return False
6054d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren
6064d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            osapipe.write(script)
6074d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            rc = osapipe.close()
6084d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren            return not rc
6094d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren
6104d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren
611e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Don't clear _tryorder or _browsers since OS X can use above Unix support
612e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # (but we prefer using the OS X specific stuff)
6134d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren    register("safari", None, MacOSXOSAScript('safari'), -1)
6144d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren    register("firefox", None, MacOSXOSAScript('firefox'), -1)
615b1a751b5c25b1969430197a6f5a58f49c1947effGuido van Rossum    register("chrome", None, MacOSXOSAScript('chrome'), -1)
6164d39f6e09a5c0a0e09eb51d678bacd1adaa3f2caRonald Oussoren    register("MacOSX", None, MacOSXOSAScript('default'), -1)
617e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
618f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond
619f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond# OK, now that we know what the default preference orders for each
620f7f185116a8274b105edc1be64ffc9c8061c7f43Eric S. Raymond# platform are, allow user to override them with the BROWSER variable.
62154f0222547b1e92cd018ef132307a6f793dc9505Raymond Hettingerif "BROWSER" in os.environ:
622e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    _userchoices = os.environ["BROWSER"].split(os.pathsep)
623e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    _userchoices.reverse()
624e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
625e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # Treat choices in same way as if passed into get() but do register
626e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    # and prepend to _tryorder
627e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    for cmdline in _userchoices:
628e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        if cmdline != '':
6298719ad5ddefadbc08b56a0af91515f050c89c678Benjamin Peterson            cmd = _synthesize(cmdline, -1)
6308719ad5ddefadbc08b56a0af91515f050c89c678Benjamin Peterson            if cmd[1] is None:
6318719ad5ddefadbc08b56a0af91515f050c89c678Benjamin Peterson                register(cmdline, None, GenericBrowser(cmdline), -1)
632e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    cmdline = None # to make del work if _userchoices was empty
633e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    del cmdline
634e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    del _userchoices
635e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
636cdab3bf7eb0810fcda21be065868f3da330779a1Skip Montanaro# what to do if _tryorder is now empty?
637e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
638e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
639e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandldef main():
640e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    import getopt
641e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    usage = """Usage: %s [-n | -t] url
642e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    -n: open new window
643e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    -t: open new tab""" % sys.argv[0]
644e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    try:
645e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        opts, args = getopt.getopt(sys.argv[1:], 'ntd')
646b940e113bf90ff71b0ef57414ea2beea9d2a4bc0Guido van Rossum    except getopt.error as msg:
647be19ed77ddb047e02fe94d142181062af6d99dccGuido van Rossum        print(msg, file=sys.stderr)
648be19ed77ddb047e02fe94d142181062af6d99dccGuido van Rossum        print(usage, file=sys.stderr)
649e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        sys.exit(1)
650e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    new_win = 0
651e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    for o, a in opts:
652e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        if o == '-n': new_win = 1
653e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        elif o == '-t': new_win = 2
654b053cd8f40dd19985b16f50661640dcefb69888fGuido van Rossum    if len(args) != 1:
655be19ed77ddb047e02fe94d142181062af6d99dccGuido van Rossum        print(usage, file=sys.stderr)
656e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl        sys.exit(1)
657e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
658e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    url = args[0]
659e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    open(url, new_win)
660e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl
661be19ed77ddb047e02fe94d142181062af6d99dccGuido van Rossum    print("\a")
66223929f2828078ee8fb115968f732d26bb9e4c1e3Georg Brandl
663e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandlif __name__ == "__main__":
664e8f244305ef4f257f6999b69601f4316b31faa5eGeorg Brandl    main()
665