|
| 1 | +""" |
| 2 | +Web browser interaction. |
| 3 | +
|
| 4 | +.. envvar:: BROWSER |
| 5 | +
|
| 6 | + A ``PATH``-like list of web browsers to try in preference order, before |
| 7 | + falling back to a set of default browsers. May be program names, e.g. |
| 8 | + ``firefox``, or absolute paths to specific executables, e.g. |
| 9 | + ``/usr/bin/firefox``. |
| 10 | +
|
| 11 | +.. envvar:: NOBROWSER |
| 12 | +
|
| 13 | + If set to a truthy value (e.g. 1) then no web browser will be considered |
| 14 | + available. This can be useful to prevent opening of a browser when there |
| 15 | + are not other means of doing so. |
| 16 | +""" |
| 17 | +import webbrowser |
| 18 | +from threading import Thread, ThreadError |
| 19 | +from os import environ |
| 20 | +from .util import warn |
| 21 | + |
| 22 | + |
| 23 | +if environ.get("NOBROWSER"): |
| 24 | + BROWSER = None |
| 25 | +else: |
| 26 | + # Avoid text-mode browsers |
| 27 | + TERM = environ.pop("TERM", None) |
| 28 | + try: |
| 29 | + BROWSER = webbrowser.get() |
| 30 | + except: |
| 31 | + BROWSER = None |
| 32 | + finally: |
| 33 | + if TERM is not None: |
| 34 | + environ["TERM"] = TERM |
| 35 | + |
| 36 | + |
| 37 | +def open_browser(url: str, new_thread: bool = True): |
| 38 | + """ |
| 39 | + Opens *url* in a web browser. |
| 40 | +
|
| 41 | + Opens in a new tab, if possible, and raises the window to the top, if |
| 42 | + possible. |
| 43 | +
|
| 44 | + Launches the browser from a separate thread by default so waiting on the |
| 45 | + browser child process doesn't block the main (or calling) thread. Set |
| 46 | + *new_thread* to False to launch from the same thread as the caller (e.g. if |
| 47 | + you've already spawned a dedicated thread or process for the browser). |
| 48 | + Note that some registered browsers launch in the background themselves, but |
| 49 | + not all do, so this feature makes launch behaviour consistent across |
| 50 | + browsers. |
| 51 | +
|
| 52 | + Prints a warning to stderr if a browser can't be found or can't be |
| 53 | + launched, as automatically opening a browser is considered a |
| 54 | + nice-but-not-necessary feature. |
| 55 | + """ |
| 56 | + if not BROWSER: |
| 57 | + warn(f"Couldn't open <{url}> in browser: no browser found") |
| 58 | + return |
| 59 | + |
| 60 | + try: |
| 61 | + if new_thread: |
| 62 | + Thread(target = open_browser, args = (url, False), daemon = True).start() |
| 63 | + else: |
| 64 | + # new = 2 means new tab, if possible |
| 65 | + BROWSER.open(url, new = 2, autoraise = True) |
| 66 | + except (ThreadError, webbrowser.Error) as err: |
| 67 | + warn(f"Couldn't open <{url}> in browser: {err!r}") |
0 commit comments