xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/webbrowser.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
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