17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci"""Interfaces for launching and remotely controlling web browsers."""
37db96d56Sopenharmony_ci# Maintained by Georg Brandl.
47db96d56Sopenharmony_ci
57db96d56Sopenharmony_ciimport os
67db96d56Sopenharmony_ciimport shlex
77db96d56Sopenharmony_ciimport shutil
87db96d56Sopenharmony_ciimport sys
97db96d56Sopenharmony_ciimport subprocess
107db96d56Sopenharmony_ciimport threading
117db96d56Sopenharmony_ciimport warnings
127db96d56Sopenharmony_ci
137db96d56Sopenharmony_ci__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ciclass Error(Exception):
167db96d56Sopenharmony_ci    pass
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci_lock = threading.RLock()
197db96d56Sopenharmony_ci_browsers = {}                  # Dictionary of available browser controllers
207db96d56Sopenharmony_ci_tryorder = None                # Preference order of available browsers
217db96d56Sopenharmony_ci_os_preferred_browser = None    # The preferred browser
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_cidef register(name, klass, instance=None, *, preferred=False):
247db96d56Sopenharmony_ci    """Register a browser connector."""
257db96d56Sopenharmony_ci    with _lock:
267db96d56Sopenharmony_ci        if _tryorder is None:
277db96d56Sopenharmony_ci            register_standard_browsers()
287db96d56Sopenharmony_ci        _browsers[name.lower()] = [klass, instance]
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_ci        # Preferred browsers go to the front of the list.
317db96d56Sopenharmony_ci        # Need to match to the default browser returned by xdg-settings, which
327db96d56Sopenharmony_ci        # may be of the form e.g. "firefox.desktop".
337db96d56Sopenharmony_ci        if preferred or (_os_preferred_browser and name in _os_preferred_browser):
347db96d56Sopenharmony_ci            _tryorder.insert(0, name)
357db96d56Sopenharmony_ci        else:
367db96d56Sopenharmony_ci            _tryorder.append(name)
377db96d56Sopenharmony_ci
387db96d56Sopenharmony_cidef get(using=None):
397db96d56Sopenharmony_ci    """Return a browser launcher instance appropriate for the environment."""
407db96d56Sopenharmony_ci    if _tryorder is None:
417db96d56Sopenharmony_ci        with _lock:
427db96d56Sopenharmony_ci            if _tryorder is None:
437db96d56Sopenharmony_ci                register_standard_browsers()
447db96d56Sopenharmony_ci    if using is not None:
457db96d56Sopenharmony_ci        alternatives = [using]
467db96d56Sopenharmony_ci    else:
477db96d56Sopenharmony_ci        alternatives = _tryorder
487db96d56Sopenharmony_ci    for browser in alternatives:
497db96d56Sopenharmony_ci        if '%s' in browser:
507db96d56Sopenharmony_ci            # User gave us a command line, split it into name and args
517db96d56Sopenharmony_ci            browser = shlex.split(browser)
527db96d56Sopenharmony_ci            if browser[-1] == '&':
537db96d56Sopenharmony_ci                return BackgroundBrowser(browser[:-1])
547db96d56Sopenharmony_ci            else:
557db96d56Sopenharmony_ci                return GenericBrowser(browser)
567db96d56Sopenharmony_ci        else:
577db96d56Sopenharmony_ci            # User gave us a browser name or path.
587db96d56Sopenharmony_ci            try:
597db96d56Sopenharmony_ci                command = _browsers[browser.lower()]
607db96d56Sopenharmony_ci            except KeyError:
617db96d56Sopenharmony_ci                command = _synthesize(browser)
627db96d56Sopenharmony_ci            if command[1] is not None:
637db96d56Sopenharmony_ci                return command[1]
647db96d56Sopenharmony_ci            elif command[0] is not None:
657db96d56Sopenharmony_ci                return command[0]()
667db96d56Sopenharmony_ci    raise Error("could not locate runnable browser")
677db96d56Sopenharmony_ci
687db96d56Sopenharmony_ci# Please note: the following definition hides a builtin function.
697db96d56Sopenharmony_ci# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
707db96d56Sopenharmony_ci# instead of "from webbrowser import *".
717db96d56Sopenharmony_ci
727db96d56Sopenharmony_cidef open(url, new=0, autoraise=True):
737db96d56Sopenharmony_ci    """Display url using the default browser.
747db96d56Sopenharmony_ci
757db96d56Sopenharmony_ci    If possible, open url in a location determined by new.
767db96d56Sopenharmony_ci    - 0: the same browser window (the default).
777db96d56Sopenharmony_ci    - 1: a new browser window.
787db96d56Sopenharmony_ci    - 2: a new browser page ("tab").
797db96d56Sopenharmony_ci    If possible, autoraise raises the window (the default) or not.
807db96d56Sopenharmony_ci    """
817db96d56Sopenharmony_ci    if _tryorder is None:
827db96d56Sopenharmony_ci        with _lock:
837db96d56Sopenharmony_ci            if _tryorder is None:
847db96d56Sopenharmony_ci                register_standard_browsers()
857db96d56Sopenharmony_ci    for name in _tryorder:
867db96d56Sopenharmony_ci        browser = get(name)
877db96d56Sopenharmony_ci        if browser.open(url, new, autoraise):
887db96d56Sopenharmony_ci            return True
897db96d56Sopenharmony_ci    return False
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_cidef open_new(url):
927db96d56Sopenharmony_ci    """Open url in a new window of the default browser.
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ci    If not possible, then open url in the only browser window.
957db96d56Sopenharmony_ci    """
967db96d56Sopenharmony_ci    return open(url, 1)
977db96d56Sopenharmony_ci
987db96d56Sopenharmony_cidef open_new_tab(url):
997db96d56Sopenharmony_ci    """Open url in a new page ("tab") of the default browser.
1007db96d56Sopenharmony_ci
1017db96d56Sopenharmony_ci    If not possible, then the behavior becomes equivalent to open_new().
1027db96d56Sopenharmony_ci    """
1037db96d56Sopenharmony_ci    return open(url, 2)
1047db96d56Sopenharmony_ci
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_cidef _synthesize(browser, *, preferred=False):
1077db96d56Sopenharmony_ci    """Attempt to synthesize a controller based on existing controllers.
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci    This is useful to create a controller when a user specifies a path to
1107db96d56Sopenharmony_ci    an entry in the BROWSER environment variable -- we can copy a general
1117db96d56Sopenharmony_ci    controller to operate using a specific installation of the desired
1127db96d56Sopenharmony_ci    browser in this way.
1137db96d56Sopenharmony_ci
1147db96d56Sopenharmony_ci    If we can't create a controller in this way, or if there is no
1157db96d56Sopenharmony_ci    executable for the requested browser, return [None, None].
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci    """
1187db96d56Sopenharmony_ci    cmd = browser.split()[0]
1197db96d56Sopenharmony_ci    if not shutil.which(cmd):
1207db96d56Sopenharmony_ci        return [None, None]
1217db96d56Sopenharmony_ci    name = os.path.basename(cmd)
1227db96d56Sopenharmony_ci    try:
1237db96d56Sopenharmony_ci        command = _browsers[name.lower()]
1247db96d56Sopenharmony_ci    except KeyError:
1257db96d56Sopenharmony_ci        return [None, None]
1267db96d56Sopenharmony_ci    # now attempt to clone to fit the new name:
1277db96d56Sopenharmony_ci    controller = command[1]
1287db96d56Sopenharmony_ci    if controller and name.lower() == controller.basename:
1297db96d56Sopenharmony_ci        import copy
1307db96d56Sopenharmony_ci        controller = copy.copy(controller)
1317db96d56Sopenharmony_ci        controller.name = browser
1327db96d56Sopenharmony_ci        controller.basename = os.path.basename(browser)
1337db96d56Sopenharmony_ci        register(browser, None, instance=controller, preferred=preferred)
1347db96d56Sopenharmony_ci        return [None, controller]
1357db96d56Sopenharmony_ci    return [None, None]
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci
1387db96d56Sopenharmony_ci# General parent classes
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ciclass BaseBrowser(object):
1417db96d56Sopenharmony_ci    """Parent class for all browsers. Do not use directly."""
1427db96d56Sopenharmony_ci
1437db96d56Sopenharmony_ci    args = ['%s']
1447db96d56Sopenharmony_ci
1457db96d56Sopenharmony_ci    def __init__(self, name=""):
1467db96d56Sopenharmony_ci        self.name = name
1477db96d56Sopenharmony_ci        self.basename = name
1487db96d56Sopenharmony_ci
1497db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
1507db96d56Sopenharmony_ci        raise NotImplementedError
1517db96d56Sopenharmony_ci
1527db96d56Sopenharmony_ci    def open_new(self, url):
1537db96d56Sopenharmony_ci        return self.open(url, 1)
1547db96d56Sopenharmony_ci
1557db96d56Sopenharmony_ci    def open_new_tab(self, url):
1567db96d56Sopenharmony_ci        return self.open(url, 2)
1577db96d56Sopenharmony_ci
1587db96d56Sopenharmony_ci
1597db96d56Sopenharmony_ciclass GenericBrowser(BaseBrowser):
1607db96d56Sopenharmony_ci    """Class for all browsers started with a command
1617db96d56Sopenharmony_ci       and without remote functionality."""
1627db96d56Sopenharmony_ci
1637db96d56Sopenharmony_ci    def __init__(self, name):
1647db96d56Sopenharmony_ci        if isinstance(name, str):
1657db96d56Sopenharmony_ci            self.name = name
1667db96d56Sopenharmony_ci            self.args = ["%s"]
1677db96d56Sopenharmony_ci        else:
1687db96d56Sopenharmony_ci            # name should be a list with arguments
1697db96d56Sopenharmony_ci            self.name = name[0]
1707db96d56Sopenharmony_ci            self.args = name[1:]
1717db96d56Sopenharmony_ci        self.basename = os.path.basename(self.name)
1727db96d56Sopenharmony_ci
1737db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
1747db96d56Sopenharmony_ci        sys.audit("webbrowser.open", url)
1757db96d56Sopenharmony_ci        cmdline = [self.name] + [arg.replace("%s", url)
1767db96d56Sopenharmony_ci                                 for arg in self.args]
1777db96d56Sopenharmony_ci        try:
1787db96d56Sopenharmony_ci            if sys.platform[:3] == 'win':
1797db96d56Sopenharmony_ci                p = subprocess.Popen(cmdline)
1807db96d56Sopenharmony_ci            else:
1817db96d56Sopenharmony_ci                p = subprocess.Popen(cmdline, close_fds=True)
1827db96d56Sopenharmony_ci            return not p.wait()
1837db96d56Sopenharmony_ci        except OSError:
1847db96d56Sopenharmony_ci            return False
1857db96d56Sopenharmony_ci
1867db96d56Sopenharmony_ci
1877db96d56Sopenharmony_ciclass BackgroundBrowser(GenericBrowser):
1887db96d56Sopenharmony_ci    """Class for all browsers which are to be started in the
1897db96d56Sopenharmony_ci       background."""
1907db96d56Sopenharmony_ci
1917db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
1927db96d56Sopenharmony_ci        cmdline = [self.name] + [arg.replace("%s", url)
1937db96d56Sopenharmony_ci                                 for arg in self.args]
1947db96d56Sopenharmony_ci        sys.audit("webbrowser.open", url)
1957db96d56Sopenharmony_ci        try:
1967db96d56Sopenharmony_ci            if sys.platform[:3] == 'win':
1977db96d56Sopenharmony_ci                p = subprocess.Popen(cmdline)
1987db96d56Sopenharmony_ci            else:
1997db96d56Sopenharmony_ci                p = subprocess.Popen(cmdline, close_fds=True,
2007db96d56Sopenharmony_ci                                     start_new_session=True)
2017db96d56Sopenharmony_ci            return (p.poll() is None)
2027db96d56Sopenharmony_ci        except OSError:
2037db96d56Sopenharmony_ci            return False
2047db96d56Sopenharmony_ci
2057db96d56Sopenharmony_ci
2067db96d56Sopenharmony_ciclass UnixBrowser(BaseBrowser):
2077db96d56Sopenharmony_ci    """Parent class for all Unix browsers with remote functionality."""
2087db96d56Sopenharmony_ci
2097db96d56Sopenharmony_ci    raise_opts = None
2107db96d56Sopenharmony_ci    background = False
2117db96d56Sopenharmony_ci    redirect_stdout = True
2127db96d56Sopenharmony_ci    # In remote_args, %s will be replaced with the requested URL.  %action will
2137db96d56Sopenharmony_ci    # be replaced depending on the value of 'new' passed to open.
2147db96d56Sopenharmony_ci    # remote_action is used for new=0 (open).  If newwin is not None, it is
2157db96d56Sopenharmony_ci    # used for new=1 (open_new).  If newtab is not None, it is used for
2167db96d56Sopenharmony_ci    # new=3 (open_new_tab).  After both substitutions are made, any empty
2177db96d56Sopenharmony_ci    # strings in the transformed remote_args list will be removed.
2187db96d56Sopenharmony_ci    remote_args = ['%action', '%s']
2197db96d56Sopenharmony_ci    remote_action = None
2207db96d56Sopenharmony_ci    remote_action_newwin = None
2217db96d56Sopenharmony_ci    remote_action_newtab = None
2227db96d56Sopenharmony_ci
2237db96d56Sopenharmony_ci    def _invoke(self, args, remote, autoraise, url=None):
2247db96d56Sopenharmony_ci        raise_opt = []
2257db96d56Sopenharmony_ci        if remote and self.raise_opts:
2267db96d56Sopenharmony_ci            # use autoraise argument only for remote invocation
2277db96d56Sopenharmony_ci            autoraise = int(autoraise)
2287db96d56Sopenharmony_ci            opt = self.raise_opts[autoraise]
2297db96d56Sopenharmony_ci            if opt: raise_opt = [opt]
2307db96d56Sopenharmony_ci
2317db96d56Sopenharmony_ci        cmdline = [self.name] + raise_opt + args
2327db96d56Sopenharmony_ci
2337db96d56Sopenharmony_ci        if remote or self.background:
2347db96d56Sopenharmony_ci            inout = subprocess.DEVNULL
2357db96d56Sopenharmony_ci        else:
2367db96d56Sopenharmony_ci            # for TTY browsers, we need stdin/out
2377db96d56Sopenharmony_ci            inout = None
2387db96d56Sopenharmony_ci        p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
2397db96d56Sopenharmony_ci                             stdout=(self.redirect_stdout and inout or None),
2407db96d56Sopenharmony_ci                             stderr=inout, start_new_session=True)
2417db96d56Sopenharmony_ci        if remote:
2427db96d56Sopenharmony_ci            # wait at most five seconds. If the subprocess is not finished, the
2437db96d56Sopenharmony_ci            # remote invocation has (hopefully) started a new instance.
2447db96d56Sopenharmony_ci            try:
2457db96d56Sopenharmony_ci                rc = p.wait(5)
2467db96d56Sopenharmony_ci                # if remote call failed, open() will try direct invocation
2477db96d56Sopenharmony_ci                return not rc
2487db96d56Sopenharmony_ci            except subprocess.TimeoutExpired:
2497db96d56Sopenharmony_ci                return True
2507db96d56Sopenharmony_ci        elif self.background:
2517db96d56Sopenharmony_ci            if p.poll() is None:
2527db96d56Sopenharmony_ci                return True
2537db96d56Sopenharmony_ci            else:
2547db96d56Sopenharmony_ci                return False
2557db96d56Sopenharmony_ci        else:
2567db96d56Sopenharmony_ci            return not p.wait()
2577db96d56Sopenharmony_ci
2587db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
2597db96d56Sopenharmony_ci        sys.audit("webbrowser.open", url)
2607db96d56Sopenharmony_ci        if new == 0:
2617db96d56Sopenharmony_ci            action = self.remote_action
2627db96d56Sopenharmony_ci        elif new == 1:
2637db96d56Sopenharmony_ci            action = self.remote_action_newwin
2647db96d56Sopenharmony_ci        elif new == 2:
2657db96d56Sopenharmony_ci            if self.remote_action_newtab is None:
2667db96d56Sopenharmony_ci                action = self.remote_action_newwin
2677db96d56Sopenharmony_ci            else:
2687db96d56Sopenharmony_ci                action = self.remote_action_newtab
2697db96d56Sopenharmony_ci        else:
2707db96d56Sopenharmony_ci            raise Error("Bad 'new' parameter to open(); " +
2717db96d56Sopenharmony_ci                        "expected 0, 1, or 2, got %s" % new)
2727db96d56Sopenharmony_ci
2737db96d56Sopenharmony_ci        args = [arg.replace("%s", url).replace("%action", action)
2747db96d56Sopenharmony_ci                for arg in self.remote_args]
2757db96d56Sopenharmony_ci        args = [arg for arg in args if arg]
2767db96d56Sopenharmony_ci        success = self._invoke(args, True, autoraise, url)
2777db96d56Sopenharmony_ci        if not success:
2787db96d56Sopenharmony_ci            # remote invocation failed, try straight way
2797db96d56Sopenharmony_ci            args = [arg.replace("%s", url) for arg in self.args]
2807db96d56Sopenharmony_ci            return self._invoke(args, False, False)
2817db96d56Sopenharmony_ci        else:
2827db96d56Sopenharmony_ci            return True
2837db96d56Sopenharmony_ci
2847db96d56Sopenharmony_ci
2857db96d56Sopenharmony_ciclass Mozilla(UnixBrowser):
2867db96d56Sopenharmony_ci    """Launcher class for Mozilla browsers."""
2877db96d56Sopenharmony_ci
2887db96d56Sopenharmony_ci    remote_args = ['%action', '%s']
2897db96d56Sopenharmony_ci    remote_action = ""
2907db96d56Sopenharmony_ci    remote_action_newwin = "-new-window"
2917db96d56Sopenharmony_ci    remote_action_newtab = "-new-tab"
2927db96d56Sopenharmony_ci    background = True
2937db96d56Sopenharmony_ci
2947db96d56Sopenharmony_ci
2957db96d56Sopenharmony_ciclass Netscape(UnixBrowser):
2967db96d56Sopenharmony_ci    """Launcher class for Netscape browser."""
2977db96d56Sopenharmony_ci
2987db96d56Sopenharmony_ci    raise_opts = ["-noraise", "-raise"]
2997db96d56Sopenharmony_ci    remote_args = ['-remote', 'openURL(%s%action)']
3007db96d56Sopenharmony_ci    remote_action = ""
3017db96d56Sopenharmony_ci    remote_action_newwin = ",new-window"
3027db96d56Sopenharmony_ci    remote_action_newtab = ",new-tab"
3037db96d56Sopenharmony_ci    background = True
3047db96d56Sopenharmony_ci
3057db96d56Sopenharmony_ci
3067db96d56Sopenharmony_ciclass Galeon(UnixBrowser):
3077db96d56Sopenharmony_ci    """Launcher class for Galeon/Epiphany browsers."""
3087db96d56Sopenharmony_ci
3097db96d56Sopenharmony_ci    raise_opts = ["-noraise", ""]
3107db96d56Sopenharmony_ci    remote_args = ['%action', '%s']
3117db96d56Sopenharmony_ci    remote_action = "-n"
3127db96d56Sopenharmony_ci    remote_action_newwin = "-w"
3137db96d56Sopenharmony_ci    background = True
3147db96d56Sopenharmony_ci
3157db96d56Sopenharmony_ci
3167db96d56Sopenharmony_ciclass Chrome(UnixBrowser):
3177db96d56Sopenharmony_ci    "Launcher class for Google Chrome browser."
3187db96d56Sopenharmony_ci
3197db96d56Sopenharmony_ci    remote_args = ['%action', '%s']
3207db96d56Sopenharmony_ci    remote_action = ""
3217db96d56Sopenharmony_ci    remote_action_newwin = "--new-window"
3227db96d56Sopenharmony_ci    remote_action_newtab = ""
3237db96d56Sopenharmony_ci    background = True
3247db96d56Sopenharmony_ci
3257db96d56Sopenharmony_ciChromium = Chrome
3267db96d56Sopenharmony_ci
3277db96d56Sopenharmony_ci
3287db96d56Sopenharmony_ciclass Opera(UnixBrowser):
3297db96d56Sopenharmony_ci    "Launcher class for Opera browser."
3307db96d56Sopenharmony_ci
3317db96d56Sopenharmony_ci    remote_args = ['%action', '%s']
3327db96d56Sopenharmony_ci    remote_action = ""
3337db96d56Sopenharmony_ci    remote_action_newwin = "--new-window"
3347db96d56Sopenharmony_ci    remote_action_newtab = ""
3357db96d56Sopenharmony_ci    background = True
3367db96d56Sopenharmony_ci
3377db96d56Sopenharmony_ci
3387db96d56Sopenharmony_ciclass Elinks(UnixBrowser):
3397db96d56Sopenharmony_ci    "Launcher class for Elinks browsers."
3407db96d56Sopenharmony_ci
3417db96d56Sopenharmony_ci    remote_args = ['-remote', 'openURL(%s%action)']
3427db96d56Sopenharmony_ci    remote_action = ""
3437db96d56Sopenharmony_ci    remote_action_newwin = ",new-window"
3447db96d56Sopenharmony_ci    remote_action_newtab = ",new-tab"
3457db96d56Sopenharmony_ci    background = False
3467db96d56Sopenharmony_ci
3477db96d56Sopenharmony_ci    # elinks doesn't like its stdout to be redirected -
3487db96d56Sopenharmony_ci    # it uses redirected stdout as a signal to do -dump
3497db96d56Sopenharmony_ci    redirect_stdout = False
3507db96d56Sopenharmony_ci
3517db96d56Sopenharmony_ci
3527db96d56Sopenharmony_ciclass Konqueror(BaseBrowser):
3537db96d56Sopenharmony_ci    """Controller for the KDE File Manager (kfm, or Konqueror).
3547db96d56Sopenharmony_ci
3557db96d56Sopenharmony_ci    See the output of ``kfmclient --commands``
3567db96d56Sopenharmony_ci    for more information on the Konqueror remote-control interface.
3577db96d56Sopenharmony_ci    """
3587db96d56Sopenharmony_ci
3597db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
3607db96d56Sopenharmony_ci        sys.audit("webbrowser.open", url)
3617db96d56Sopenharmony_ci        # XXX Currently I know no way to prevent KFM from opening a new win.
3627db96d56Sopenharmony_ci        if new == 2:
3637db96d56Sopenharmony_ci            action = "newTab"
3647db96d56Sopenharmony_ci        else:
3657db96d56Sopenharmony_ci            action = "openURL"
3667db96d56Sopenharmony_ci
3677db96d56Sopenharmony_ci        devnull = subprocess.DEVNULL
3687db96d56Sopenharmony_ci
3697db96d56Sopenharmony_ci        try:
3707db96d56Sopenharmony_ci            p = subprocess.Popen(["kfmclient", action, url],
3717db96d56Sopenharmony_ci                                 close_fds=True, stdin=devnull,
3727db96d56Sopenharmony_ci                                 stdout=devnull, stderr=devnull)
3737db96d56Sopenharmony_ci        except OSError:
3747db96d56Sopenharmony_ci            # fall through to next variant
3757db96d56Sopenharmony_ci            pass
3767db96d56Sopenharmony_ci        else:
3777db96d56Sopenharmony_ci            p.wait()
3787db96d56Sopenharmony_ci            # kfmclient's return code unfortunately has no meaning as it seems
3797db96d56Sopenharmony_ci            return True
3807db96d56Sopenharmony_ci
3817db96d56Sopenharmony_ci        try:
3827db96d56Sopenharmony_ci            p = subprocess.Popen(["konqueror", "--silent", url],
3837db96d56Sopenharmony_ci                                 close_fds=True, stdin=devnull,
3847db96d56Sopenharmony_ci                                 stdout=devnull, stderr=devnull,
3857db96d56Sopenharmony_ci                                 start_new_session=True)
3867db96d56Sopenharmony_ci        except OSError:
3877db96d56Sopenharmony_ci            # fall through to next variant
3887db96d56Sopenharmony_ci            pass
3897db96d56Sopenharmony_ci        else:
3907db96d56Sopenharmony_ci            if p.poll() is None:
3917db96d56Sopenharmony_ci                # Should be running now.
3927db96d56Sopenharmony_ci                return True
3937db96d56Sopenharmony_ci
3947db96d56Sopenharmony_ci        try:
3957db96d56Sopenharmony_ci            p = subprocess.Popen(["kfm", "-d", url],
3967db96d56Sopenharmony_ci                                 close_fds=True, stdin=devnull,
3977db96d56Sopenharmony_ci                                 stdout=devnull, stderr=devnull,
3987db96d56Sopenharmony_ci                                 start_new_session=True)
3997db96d56Sopenharmony_ci        except OSError:
4007db96d56Sopenharmony_ci            return False
4017db96d56Sopenharmony_ci        else:
4027db96d56Sopenharmony_ci            return (p.poll() is None)
4037db96d56Sopenharmony_ci
4047db96d56Sopenharmony_ci
4057db96d56Sopenharmony_ciclass Grail(BaseBrowser):
4067db96d56Sopenharmony_ci    # There should be a way to maintain a connection to Grail, but the
4077db96d56Sopenharmony_ci    # Grail remote control protocol doesn't really allow that at this
4087db96d56Sopenharmony_ci    # point.  It probably never will!
4097db96d56Sopenharmony_ci    def _find_grail_rc(self):
4107db96d56Sopenharmony_ci        import glob
4117db96d56Sopenharmony_ci        import pwd
4127db96d56Sopenharmony_ci        import socket
4137db96d56Sopenharmony_ci        import tempfile
4147db96d56Sopenharmony_ci        tempdir = os.path.join(tempfile.gettempdir(),
4157db96d56Sopenharmony_ci                               ".grail-unix")
4167db96d56Sopenharmony_ci        user = pwd.getpwuid(os.getuid())[0]
4177db96d56Sopenharmony_ci        filename = os.path.join(glob.escape(tempdir), glob.escape(user) + "-*")
4187db96d56Sopenharmony_ci        maybes = glob.glob(filename)
4197db96d56Sopenharmony_ci        if not maybes:
4207db96d56Sopenharmony_ci            return None
4217db96d56Sopenharmony_ci        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
4227db96d56Sopenharmony_ci        for fn in maybes:
4237db96d56Sopenharmony_ci            # need to PING each one until we find one that's live
4247db96d56Sopenharmony_ci            try:
4257db96d56Sopenharmony_ci                s.connect(fn)
4267db96d56Sopenharmony_ci            except OSError:
4277db96d56Sopenharmony_ci                # no good; attempt to clean it out, but don't fail:
4287db96d56Sopenharmony_ci                try:
4297db96d56Sopenharmony_ci                    os.unlink(fn)
4307db96d56Sopenharmony_ci                except OSError:
4317db96d56Sopenharmony_ci                    pass
4327db96d56Sopenharmony_ci            else:
4337db96d56Sopenharmony_ci                return s
4347db96d56Sopenharmony_ci
4357db96d56Sopenharmony_ci    def _remote(self, action):
4367db96d56Sopenharmony_ci        s = self._find_grail_rc()
4377db96d56Sopenharmony_ci        if not s:
4387db96d56Sopenharmony_ci            return 0
4397db96d56Sopenharmony_ci        s.send(action)
4407db96d56Sopenharmony_ci        s.close()
4417db96d56Sopenharmony_ci        return 1
4427db96d56Sopenharmony_ci
4437db96d56Sopenharmony_ci    def open(self, url, new=0, autoraise=True):
4447db96d56Sopenharmony_ci        sys.audit("webbrowser.open", url)
4457db96d56Sopenharmony_ci        if new:
4467db96d56Sopenharmony_ci            ok = self._remote("LOADNEW " + url)
4477db96d56Sopenharmony_ci        else:
4487db96d56Sopenharmony_ci            ok = self._remote("LOAD " + url)
4497db96d56Sopenharmony_ci        return ok
4507db96d56Sopenharmony_ci
4517db96d56Sopenharmony_ci
4527db96d56Sopenharmony_ci#
4537db96d56Sopenharmony_ci# Platform support for Unix
4547db96d56Sopenharmony_ci#
4557db96d56Sopenharmony_ci
4567db96d56Sopenharmony_ci# These are the right tests because all these Unix browsers require either
4577db96d56Sopenharmony_ci# a console terminal or an X display to run.
4587db96d56Sopenharmony_ci
4597db96d56Sopenharmony_cidef register_X_browsers():
4607db96d56Sopenharmony_ci
4617db96d56Sopenharmony_ci    # use xdg-open if around
4627db96d56Sopenharmony_ci    if shutil.which("xdg-open"):
4637db96d56Sopenharmony_ci        register("xdg-open", None, BackgroundBrowser("xdg-open"))
4647db96d56Sopenharmony_ci
4657db96d56Sopenharmony_ci    # Opens an appropriate browser for the URL scheme according to
4667db96d56Sopenharmony_ci    # freedesktop.org settings (GNOME, KDE, XFCE, etc.)
4677db96d56Sopenharmony_ci    if shutil.which("gio"):
4687db96d56Sopenharmony_ci        register("gio", None, BackgroundBrowser(["gio", "open", "--", "%s"]))
4697db96d56Sopenharmony_ci
4707db96d56Sopenharmony_ci    # Equivalent of gio open before 2015
4717db96d56Sopenharmony_ci    if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
4727db96d56Sopenharmony_ci        register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
4737db96d56Sopenharmony_ci
4747db96d56Sopenharmony_ci    # The default KDE browser
4757db96d56Sopenharmony_ci    if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
4767db96d56Sopenharmony_ci        register("kfmclient", Konqueror, Konqueror("kfmclient"))
4777db96d56Sopenharmony_ci
4787db96d56Sopenharmony_ci    if shutil.which("x-www-browser"):
4797db96d56Sopenharmony_ci        register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
4807db96d56Sopenharmony_ci
4817db96d56Sopenharmony_ci    # The Mozilla browsers
4827db96d56Sopenharmony_ci    for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
4837db96d56Sopenharmony_ci        if shutil.which(browser):
4847db96d56Sopenharmony_ci            register(browser, None, Mozilla(browser))
4857db96d56Sopenharmony_ci
4867db96d56Sopenharmony_ci    # The Netscape and old Mozilla browsers
4877db96d56Sopenharmony_ci    for browser in ("mozilla-firefox",
4887db96d56Sopenharmony_ci                    "mozilla-firebird", "firebird",
4897db96d56Sopenharmony_ci                    "mozilla", "netscape"):
4907db96d56Sopenharmony_ci        if shutil.which(browser):
4917db96d56Sopenharmony_ci            register(browser, None, Netscape(browser))
4927db96d56Sopenharmony_ci
4937db96d56Sopenharmony_ci    # Konqueror/kfm, the KDE browser.
4947db96d56Sopenharmony_ci    if shutil.which("kfm"):
4957db96d56Sopenharmony_ci        register("kfm", Konqueror, Konqueror("kfm"))
4967db96d56Sopenharmony_ci    elif shutil.which("konqueror"):
4977db96d56Sopenharmony_ci        register("konqueror", Konqueror, Konqueror("konqueror"))
4987db96d56Sopenharmony_ci
4997db96d56Sopenharmony_ci    # Gnome's Galeon and Epiphany
5007db96d56Sopenharmony_ci    for browser in ("galeon", "epiphany"):
5017db96d56Sopenharmony_ci        if shutil.which(browser):
5027db96d56Sopenharmony_ci            register(browser, None, Galeon(browser))
5037db96d56Sopenharmony_ci
5047db96d56Sopenharmony_ci    # Skipstone, another Gtk/Mozilla based browser
5057db96d56Sopenharmony_ci    if shutil.which("skipstone"):
5067db96d56Sopenharmony_ci        register("skipstone", None, BackgroundBrowser("skipstone"))
5077db96d56Sopenharmony_ci
5087db96d56Sopenharmony_ci    # Google Chrome/Chromium browsers
5097db96d56Sopenharmony_ci    for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
5107db96d56Sopenharmony_ci        if shutil.which(browser):
5117db96d56Sopenharmony_ci            register(browser, None, Chrome(browser))
5127db96d56Sopenharmony_ci
5137db96d56Sopenharmony_ci    # Opera, quite popular
5147db96d56Sopenharmony_ci    if shutil.which("opera"):
5157db96d56Sopenharmony_ci        register("opera", None, Opera("opera"))
5167db96d56Sopenharmony_ci
5177db96d56Sopenharmony_ci    # Next, Mosaic -- old but still in use.
5187db96d56Sopenharmony_ci    if shutil.which("mosaic"):
5197db96d56Sopenharmony_ci        register("mosaic", None, BackgroundBrowser("mosaic"))
5207db96d56Sopenharmony_ci
5217db96d56Sopenharmony_ci    # Grail, the Python browser. Does anybody still use it?
5227db96d56Sopenharmony_ci    if shutil.which("grail"):
5237db96d56Sopenharmony_ci        register("grail", Grail, None)
5247db96d56Sopenharmony_ci
5257db96d56Sopenharmony_cidef register_standard_browsers():
5267db96d56Sopenharmony_ci    global _tryorder
5277db96d56Sopenharmony_ci    _tryorder = []
5287db96d56Sopenharmony_ci
5297db96d56Sopenharmony_ci    if sys.platform == 'darwin':
5307db96d56Sopenharmony_ci        register("MacOSX", None, MacOSXOSAScript('default'))
5317db96d56Sopenharmony_ci        register("chrome", None, MacOSXOSAScript('chrome'))
5327db96d56Sopenharmony_ci        register("firefox", None, MacOSXOSAScript('firefox'))
5337db96d56Sopenharmony_ci        register("safari", None, MacOSXOSAScript('safari'))
5347db96d56Sopenharmony_ci        # OS X can use below Unix support (but we prefer using the OS X
5357db96d56Sopenharmony_ci        # specific stuff)
5367db96d56Sopenharmony_ci
5377db96d56Sopenharmony_ci    if sys.platform == "serenityos":
5387db96d56Sopenharmony_ci        # SerenityOS webbrowser, simply called "Browser".
5397db96d56Sopenharmony_ci        register("Browser", None, BackgroundBrowser("Browser"))
5407db96d56Sopenharmony_ci
5417db96d56Sopenharmony_ci    if sys.platform[:3] == "win":
5427db96d56Sopenharmony_ci        # First try to use the default Windows browser
5437db96d56Sopenharmony_ci        register("windows-default", WindowsDefault)
5447db96d56Sopenharmony_ci
5457db96d56Sopenharmony_ci        # Detect some common Windows browsers, fallback to IE
5467db96d56Sopenharmony_ci        iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
5477db96d56Sopenharmony_ci                                "Internet Explorer\\IEXPLORE.EXE")
5487db96d56Sopenharmony_ci        for browser in ("firefox", "firebird", "seamonkey", "mozilla",
5497db96d56Sopenharmony_ci                        "netscape", "opera", iexplore):
5507db96d56Sopenharmony_ci            if shutil.which(browser):
5517db96d56Sopenharmony_ci                register(browser, None, BackgroundBrowser(browser))
5527db96d56Sopenharmony_ci    else:
5537db96d56Sopenharmony_ci        # Prefer X browsers if present
5547db96d56Sopenharmony_ci        if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
5557db96d56Sopenharmony_ci            try:
5567db96d56Sopenharmony_ci                cmd = "xdg-settings get default-web-browser".split()
5577db96d56Sopenharmony_ci                raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
5587db96d56Sopenharmony_ci                result = raw_result.decode().strip()
5597db96d56Sopenharmony_ci            except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) :
5607db96d56Sopenharmony_ci                pass
5617db96d56Sopenharmony_ci            else:
5627db96d56Sopenharmony_ci                global _os_preferred_browser
5637db96d56Sopenharmony_ci                _os_preferred_browser = result
5647db96d56Sopenharmony_ci
5657db96d56Sopenharmony_ci            register_X_browsers()
5667db96d56Sopenharmony_ci
5677db96d56Sopenharmony_ci        # Also try console browsers
5687db96d56Sopenharmony_ci        if os.environ.get("TERM"):
5697db96d56Sopenharmony_ci            if shutil.which("www-browser"):
5707db96d56Sopenharmony_ci                register("www-browser", None, GenericBrowser("www-browser"))
5717db96d56Sopenharmony_ci            # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
5727db96d56Sopenharmony_ci            if shutil.which("links"):
5737db96d56Sopenharmony_ci                register("links", None, GenericBrowser("links"))
5747db96d56Sopenharmony_ci            if shutil.which("elinks"):
5757db96d56Sopenharmony_ci                register("elinks", None, Elinks("elinks"))
5767db96d56Sopenharmony_ci            # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
5777db96d56Sopenharmony_ci            if shutil.which("lynx"):
5787db96d56Sopenharmony_ci                register("lynx", None, GenericBrowser("lynx"))
5797db96d56Sopenharmony_ci            # The w3m browser <http://w3m.sourceforge.net/>
5807db96d56Sopenharmony_ci            if shutil.which("w3m"):
5817db96d56Sopenharmony_ci                register("w3m", None, GenericBrowser("w3m"))
5827db96d56Sopenharmony_ci
5837db96d56Sopenharmony_ci    # OK, now that we know what the default preference orders for each
5847db96d56Sopenharmony_ci    # platform are, allow user to override them with the BROWSER variable.
5857db96d56Sopenharmony_ci    if "BROWSER" in os.environ:
5867db96d56Sopenharmony_ci        userchoices = os.environ["BROWSER"].split(os.pathsep)
5877db96d56Sopenharmony_ci        userchoices.reverse()
5887db96d56Sopenharmony_ci
5897db96d56Sopenharmony_ci        # Treat choices in same way as if passed into get() but do register
5907db96d56Sopenharmony_ci        # and prepend to _tryorder
5917db96d56Sopenharmony_ci        for cmdline in userchoices:
5927db96d56Sopenharmony_ci            if cmdline != '':
5937db96d56Sopenharmony_ci                cmd = _synthesize(cmdline, preferred=True)
5947db96d56Sopenharmony_ci                if cmd[1] is None:
5957db96d56Sopenharmony_ci                    register(cmdline, None, GenericBrowser(cmdline), preferred=True)
5967db96d56Sopenharmony_ci
5977db96d56Sopenharmony_ci    # what to do if _tryorder is now empty?
5987db96d56Sopenharmony_ci
5997db96d56Sopenharmony_ci
6007db96d56Sopenharmony_ci#
6017db96d56Sopenharmony_ci# Platform support for Windows
6027db96d56Sopenharmony_ci#
6037db96d56Sopenharmony_ci
6047db96d56Sopenharmony_ciif sys.platform[:3] == "win":
6057db96d56Sopenharmony_ci    class WindowsDefault(BaseBrowser):
6067db96d56Sopenharmony_ci        def open(self, url, new=0, autoraise=True):
6077db96d56Sopenharmony_ci            sys.audit("webbrowser.open", url)
6087db96d56Sopenharmony_ci            try:
6097db96d56Sopenharmony_ci                os.startfile(url)
6107db96d56Sopenharmony_ci            except OSError:
6117db96d56Sopenharmony_ci                # [Error 22] No application is associated with the specified
6127db96d56Sopenharmony_ci                # file for this operation: '<URL>'
6137db96d56Sopenharmony_ci                return False
6147db96d56Sopenharmony_ci            else:
6157db96d56Sopenharmony_ci                return True
6167db96d56Sopenharmony_ci
6177db96d56Sopenharmony_ci#
6187db96d56Sopenharmony_ci# Platform support for MacOS
6197db96d56Sopenharmony_ci#
6207db96d56Sopenharmony_ci
6217db96d56Sopenharmony_ciif sys.platform == 'darwin':
6227db96d56Sopenharmony_ci    # Adapted from patch submitted to SourceForge by Steven J. Burr
6237db96d56Sopenharmony_ci    class MacOSX(BaseBrowser):
6247db96d56Sopenharmony_ci        """Launcher class for Aqua browsers on Mac OS X
6257db96d56Sopenharmony_ci
6267db96d56Sopenharmony_ci        Optionally specify a browser name on instantiation.  Note that this
6277db96d56Sopenharmony_ci        will not work for Aqua browsers if the user has moved the application
6287db96d56Sopenharmony_ci        package after installation.
6297db96d56Sopenharmony_ci
6307db96d56Sopenharmony_ci        If no browser is specified, the default browser, as specified in the
6317db96d56Sopenharmony_ci        Internet System Preferences panel, will be used.
6327db96d56Sopenharmony_ci        """
6337db96d56Sopenharmony_ci        def __init__(self, name):
6347db96d56Sopenharmony_ci            warnings.warn(f'{self.__class__.__name__} is deprecated in 3.11'
6357db96d56Sopenharmony_ci                          ' use MacOSXOSAScript instead.', DeprecationWarning, stacklevel=2)
6367db96d56Sopenharmony_ci            self.name = name
6377db96d56Sopenharmony_ci
6387db96d56Sopenharmony_ci        def open(self, url, new=0, autoraise=True):
6397db96d56Sopenharmony_ci            sys.audit("webbrowser.open", url)
6407db96d56Sopenharmony_ci            assert "'" not in url
6417db96d56Sopenharmony_ci            # hack for local urls
6427db96d56Sopenharmony_ci            if not ':' in url:
6437db96d56Sopenharmony_ci                url = 'file:'+url
6447db96d56Sopenharmony_ci
6457db96d56Sopenharmony_ci            # new must be 0 or 1
6467db96d56Sopenharmony_ci            new = int(bool(new))
6477db96d56Sopenharmony_ci            if self.name == "default":
6487db96d56Sopenharmony_ci                # User called open, open_new or get without a browser parameter
6497db96d56Sopenharmony_ci                script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
6507db96d56Sopenharmony_ci            else:
6517db96d56Sopenharmony_ci                # User called get and chose a browser
6527db96d56Sopenharmony_ci                if self.name == "OmniWeb":
6537db96d56Sopenharmony_ci                    toWindow = ""
6547db96d56Sopenharmony_ci                else:
6557db96d56Sopenharmony_ci                    # Include toWindow parameter of OpenURL command for browsers
6567db96d56Sopenharmony_ci                    # that support it.  0 == new window; -1 == existing
6577db96d56Sopenharmony_ci                    toWindow = "toWindow %d" % (new - 1)
6587db96d56Sopenharmony_ci                cmd = 'OpenURL "%s"' % url.replace('"', '%22')
6597db96d56Sopenharmony_ci                script = '''tell application "%s"
6607db96d56Sopenharmony_ci                                activate
6617db96d56Sopenharmony_ci                                %s %s
6627db96d56Sopenharmony_ci                            end tell''' % (self.name, cmd, toWindow)
6637db96d56Sopenharmony_ci            # Open pipe to AppleScript through osascript command
6647db96d56Sopenharmony_ci            osapipe = os.popen("osascript", "w")
6657db96d56Sopenharmony_ci            if osapipe is None:
6667db96d56Sopenharmony_ci                return False
6677db96d56Sopenharmony_ci            # Write script to osascript's stdin
6687db96d56Sopenharmony_ci            osapipe.write(script)
6697db96d56Sopenharmony_ci            rc = osapipe.close()
6707db96d56Sopenharmony_ci            return not rc
6717db96d56Sopenharmony_ci
6727db96d56Sopenharmony_ci    class MacOSXOSAScript(BaseBrowser):
6737db96d56Sopenharmony_ci        def __init__(self, name='default'):
6747db96d56Sopenharmony_ci            super().__init__(name)
6757db96d56Sopenharmony_ci
6767db96d56Sopenharmony_ci        @property
6777db96d56Sopenharmony_ci        def _name(self):
6787db96d56Sopenharmony_ci            warnings.warn(f'{self.__class__.__name__}._name is deprecated in 3.11'
6797db96d56Sopenharmony_ci                          f' use {self.__class__.__name__}.name instead.',
6807db96d56Sopenharmony_ci                          DeprecationWarning, stacklevel=2)
6817db96d56Sopenharmony_ci            return self.name
6827db96d56Sopenharmony_ci
6837db96d56Sopenharmony_ci        @_name.setter
6847db96d56Sopenharmony_ci        def _name(self, val):
6857db96d56Sopenharmony_ci            warnings.warn(f'{self.__class__.__name__}._name is deprecated in 3.11'
6867db96d56Sopenharmony_ci                          f' use {self.__class__.__name__}.name instead.',
6877db96d56Sopenharmony_ci                          DeprecationWarning, stacklevel=2)
6887db96d56Sopenharmony_ci            self.name = val
6897db96d56Sopenharmony_ci
6907db96d56Sopenharmony_ci        def open(self, url, new=0, autoraise=True):
6917db96d56Sopenharmony_ci            if self.name == 'default':
6927db96d56Sopenharmony_ci                script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
6937db96d56Sopenharmony_ci            else:
6947db96d56Sopenharmony_ci                script = f'''
6957db96d56Sopenharmony_ci                   tell application "%s"
6967db96d56Sopenharmony_ci                       activate
6977db96d56Sopenharmony_ci                       open location "%s"
6987db96d56Sopenharmony_ci                   end
6997db96d56Sopenharmony_ci                   '''%(self.name, url.replace('"', '%22'))
7007db96d56Sopenharmony_ci
7017db96d56Sopenharmony_ci            osapipe = os.popen("osascript", "w")
7027db96d56Sopenharmony_ci            if osapipe is None:
7037db96d56Sopenharmony_ci                return False
7047db96d56Sopenharmony_ci
7057db96d56Sopenharmony_ci            osapipe.write(script)
7067db96d56Sopenharmony_ci            rc = osapipe.close()
7077db96d56Sopenharmony_ci            return not rc
7087db96d56Sopenharmony_ci
7097db96d56Sopenharmony_ci
7107db96d56Sopenharmony_cidef main():
7117db96d56Sopenharmony_ci    import getopt
7127db96d56Sopenharmony_ci    usage = """Usage: %s [-n | -t] url
7137db96d56Sopenharmony_ci    -n: open new window
7147db96d56Sopenharmony_ci    -t: open new tab""" % sys.argv[0]
7157db96d56Sopenharmony_ci    try:
7167db96d56Sopenharmony_ci        opts, args = getopt.getopt(sys.argv[1:], 'ntd')
7177db96d56Sopenharmony_ci    except getopt.error as msg:
7187db96d56Sopenharmony_ci        print(msg, file=sys.stderr)
7197db96d56Sopenharmony_ci        print(usage, file=sys.stderr)
7207db96d56Sopenharmony_ci        sys.exit(1)
7217db96d56Sopenharmony_ci    new_win = 0
7227db96d56Sopenharmony_ci    for o, a in opts:
7237db96d56Sopenharmony_ci        if o == '-n': new_win = 1
7247db96d56Sopenharmony_ci        elif o == '-t': new_win = 2
7257db96d56Sopenharmony_ci    if len(args) != 1:
7267db96d56Sopenharmony_ci        print(usage, file=sys.stderr)
7277db96d56Sopenharmony_ci        sys.exit(1)
7287db96d56Sopenharmony_ci
7297db96d56Sopenharmony_ci    url = args[0]
7307db96d56Sopenharmony_ci    open(url, new_win)
7317db96d56Sopenharmony_ci
7327db96d56Sopenharmony_ci    print("\a")
7337db96d56Sopenharmony_ci
7347db96d56Sopenharmony_ciif __name__ == "__main__":
7357db96d56Sopenharmony_ci    main()
736