blob: ec3cece48c9587b73913b1838f49dab2f04afd1c [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Miss Islington (bot)6fc1efa2021-07-26 15:34:32 -07002"""Interfaces for launching and remotely controlling web browsers."""
Guido van Rossum992d4a32007-07-11 13:09:30 +00003# Maintained by Georg Brandl.
Fred Drakec70b4482000-07-09 16:45:56 +00004
5import os
Guido van Rossumd8faa362007-04-27 19:54:29 +00006import shlex
Serhiy Storchaka540dcba2013-02-13 12:19:40 +02007import shutil
Fred Drakec70b4482000-07-09 16:45:56 +00008import sys
Georg Brandl23929f22006-01-20 21:03:35 +00009import subprocess
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020010import threading
Fred Drakec70b4482000-07-09 16:45:56 +000011
Georg Brandle8f24432005-10-03 14:16:44 +000012__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
Skip Montanaro40fc1602001-03-01 04:27:19 +000013
Fred Drakec70b4482000-07-09 16:45:56 +000014class Error(Exception):
15 pass
16
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020017_lock = threading.RLock()
David Steelee3ce6952017-02-24 23:47:38 -050018_browsers = {} # Dictionary of available browser controllers
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020019_tryorder = None # Preference order of available browsers
David Steelee3ce6952017-02-24 23:47:38 -050020_os_preferred_browser = None # The preferred browser
Fred Drakec70b4482000-07-09 16:45:56 +000021
David Steelee3ce6952017-02-24 23:47:38 -050022def register(name, klass, instance=None, *, preferred=False):
23 """Register a browser connector."""
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020024 with _lock:
25 if _tryorder is None:
26 register_standard_browsers()
27 _browsers[name.lower()] = [klass, instance]
David Steelee3ce6952017-02-24 23:47:38 -050028
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020029 # Preferred browsers go to the front of the list.
30 # Need to match to the default browser returned by xdg-settings, which
31 # may be of the form e.g. "firefox.desktop".
32 if preferred or (_os_preferred_browser and name in _os_preferred_browser):
33 _tryorder.insert(0, name)
34 else:
35 _tryorder.append(name)
Fred Drakec70b4482000-07-09 16:45:56 +000036
Eric S. Raymondf7f18512001-01-23 13:16:32 +000037def get(using=None):
38 """Return a browser launcher instance appropriate for the environment."""
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020039 if _tryorder is None:
40 with _lock:
41 if _tryorder is None:
42 register_standard_browsers()
Raymond Hettinger10ff7062002-06-02 03:04:52 +000043 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000044 alternatives = [using]
45 else:
46 alternatives = _tryorder
47 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000048 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000049 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000050 browser = shlex.split(browser)
51 if browser[-1] == '&':
52 return BackgroundBrowser(browser[:-1])
53 else:
54 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000055 else:
Georg Brandle8f24432005-10-03 14:16:44 +000056 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000057 try:
58 command = _browsers[browser.lower()]
59 except KeyError:
60 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000061 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000062 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000063 elif command[0] is not None:
64 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000065 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000066
67# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000068# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
69# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000070
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000071def open(url, new=0, autoraise=True):
Brad Solomonef7973a2020-05-11 14:50:11 -040072 """Display url using the default browser.
73
74 If possible, open url in a location determined by new.
75 - 0: the same browser window (the default).
76 - 1: a new browser window.
77 - 2: a new browser page ("tab").
78 If possible, autoraise raises the window (the default) or not.
79 """
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020080 if _tryorder is None:
81 with _lock:
82 if _tryorder is None:
83 register_standard_browsers()
Georg Brandle8f24432005-10-03 14:16:44 +000084 for name in _tryorder:
85 browser = get(name)
86 if browser.open(url, new, autoraise):
87 return True
88 return False
Fred Drakec70b4482000-07-09 16:45:56 +000089
Fred Drake3f8f1642001-07-19 03:46:26 +000090def open_new(url):
Brad Solomonef7973a2020-05-11 14:50:11 -040091 """Open url in a new window of the default browser.
92
93 If not possible, then open url in the only browser window.
94 """
Georg Brandle8f24432005-10-03 14:16:44 +000095 return open(url, 1)
96
97def open_new_tab(url):
Brad Solomonef7973a2020-05-11 14:50:11 -040098 """Open url in a new page ("tab") of the default browser.
99
100 If not possible, then the behavior becomes equivalent to open_new().
101 """
Georg Brandle8f24432005-10-03 14:16:44 +0000102 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +0000103
Fred Drakef4e5bd92001-04-12 22:07:27 +0000104
Zhiming Wang8c281ed2018-11-26 16:29:45 -0500105def _synthesize(browser, *, preferred=False):
Michael Haas558f0782020-01-19 04:29:42 -0600106 """Attempt to synthesize a controller based on existing controllers.
Fred Drakef4e5bd92001-04-12 22:07:27 +0000107
108 This is useful to create a controller when a user specifies a path to
109 an entry in the BROWSER environment variable -- we can copy a general
110 controller to operate using a specific installation of the desired
111 browser in this way.
112
113 If we can't create a controller in this way, or if there is no
114 executable for the requested browser, return [None, None].
115
116 """
Georg Brandle8f24432005-10-03 14:16:44 +0000117 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200118 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000119 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +0000120 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000121 try:
122 command = _browsers[name.lower()]
123 except KeyError:
124 return [None, None]
125 # now attempt to clone to fit the new name:
126 controller = command[1]
127 if controller and name.lower() == controller.basename:
128 import copy
129 controller = copy.copy(controller)
130 controller.name = browser
131 controller.basename = os.path.basename(browser)
Serhiy Storchaka25b804a2018-07-08 10:22:32 +0300132 register(browser, None, instance=controller, preferred=preferred)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000133 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000134 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000135
Fred Drake3f8f1642001-07-19 03:46:26 +0000136
Georg Brandle8f24432005-10-03 14:16:44 +0000137# General parent classes
138
139class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000140 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000141
Georg Brandl23929f22006-01-20 21:03:35 +0000142 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000143
Georg Brandle8f24432005-10-03 14:16:44 +0000144 def __init__(self, name=""):
145 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000146 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000147
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000148 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000149 raise NotImplementedError
150
Georg Brandle8f24432005-10-03 14:16:44 +0000151 def open_new(self, url):
152 return self.open(url, 1)
153
154 def open_new_tab(self, url):
155 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000156
157
Georg Brandle8f24432005-10-03 14:16:44 +0000158class GenericBrowser(BaseBrowser):
159 """Class for all browsers started with a command
160 and without remote functionality."""
161
Georg Brandl23929f22006-01-20 21:03:35 +0000162 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000163 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000164 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000165 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000166 else:
167 # name should be a list with arguments
168 self.name = name[0]
169 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000170 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000171
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000172 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700173 sys.audit("webbrowser.open", url)
Tim Peters887c0802006-01-20 23:40:56 +0000174 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000175 for arg in self.args]
176 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000177 if sys.platform[:3] == 'win':
178 p = subprocess.Popen(cmdline)
179 else:
180 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000181 return not p.wait()
182 except OSError:
183 return False
184
185
186class BackgroundBrowser(GenericBrowser):
187 """Class for all browsers which are to be started in the
188 background."""
189
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000190 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000191 cmdline = [self.name] + [arg.replace("%s", url)
192 for arg in self.args]
Steve Dower60419a72019-06-24 08:42:54 -0700193 sys.audit("webbrowser.open", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000194 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000195 if sys.platform[:3] == 'win':
196 p = subprocess.Popen(cmdline)
197 else:
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700198 p = subprocess.Popen(cmdline, close_fds=True,
199 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000200 return (p.poll() is None)
201 except OSError:
202 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000203
204
Georg Brandle8f24432005-10-03 14:16:44 +0000205class UnixBrowser(BaseBrowser):
206 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000207
Georg Brandle8f24432005-10-03 14:16:44 +0000208 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400209 background = False
210 redirect_stdout = True
211 # In remote_args, %s will be replaced with the requested URL. %action will
212 # be replaced depending on the value of 'new' passed to open.
213 # remote_action is used for new=0 (open). If newwin is not None, it is
214 # used for new=1 (open_new). If newtab is not None, it is used for
215 # new=3 (open_new_tab). After both substitutions are made, any empty
216 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000217 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000218 remote_action = None
219 remote_action_newwin = None
220 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000221
Steve Dower60419a72019-06-24 08:42:54 -0700222 def _invoke(self, args, remote, autoraise, url=None):
Georg Brandl23929f22006-01-20 21:03:35 +0000223 raise_opt = []
224 if remote and self.raise_opts:
225 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000226 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000227 opt = self.raise_opts[autoraise]
228 if opt: raise_opt = [opt]
229
230 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000231
Georg Brandl23929f22006-01-20 21:03:35 +0000232 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400233 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000234 else:
235 # for TTY browsers, we need stdin/out
236 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000237 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
238 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400239 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000240 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200241 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000242 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200243 try:
244 rc = p.wait(5)
245 # if remote call failed, open() will try direct invocation
246 return not rc
247 except subprocess.TimeoutExpired:
248 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000249 elif self.background:
250 if p.poll() is None:
251 return True
252 else:
253 return False
254 else:
255 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000256
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000257 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700258 sys.audit("webbrowser.open", url)
Georg Brandle8f24432005-10-03 14:16:44 +0000259 if new == 0:
260 action = self.remote_action
261 elif new == 1:
262 action = self.remote_action_newwin
263 elif new == 2:
264 if self.remote_action_newtab is None:
265 action = self.remote_action_newwin
266 else:
267 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000268 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000269 raise Error("Bad 'new' parameter to open(); " +
270 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000271
Georg Brandl23929f22006-01-20 21:03:35 +0000272 args = [arg.replace("%s", url).replace("%action", action)
273 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400274 args = [arg for arg in args if arg]
Steve Dower60419a72019-06-24 08:42:54 -0700275 success = self._invoke(args, True, autoraise, url)
Georg Brandl23929f22006-01-20 21:03:35 +0000276 if not success:
277 # remote invocation failed, try straight way
278 args = [arg.replace("%s", url) for arg in self.args]
279 return self._invoke(args, False, False)
280 else:
281 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000282
283
Georg Brandle8f24432005-10-03 14:16:44 +0000284class Mozilla(UnixBrowser):
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200285 """Launcher class for Mozilla browsers."""
286
287 remote_args = ['%action', '%s']
288 remote_action = ""
289 remote_action_newwin = "-new-window"
290 remote_action_newtab = "-new-tab"
291 background = True
292
293
294class Netscape(UnixBrowser):
295 """Launcher class for Netscape browser."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000296
Georg Brandl23929f22006-01-20 21:03:35 +0000297 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000298 remote_args = ['-remote', 'openURL(%s%action)']
299 remote_action = ""
300 remote_action_newwin = ",new-window"
301 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000302 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000303
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000304
Georg Brandle8f24432005-10-03 14:16:44 +0000305class Galeon(UnixBrowser):
306 """Launcher class for Galeon/Epiphany browsers."""
307
Georg Brandl23929f22006-01-20 21:03:35 +0000308 raise_opts = ["-noraise", ""]
309 remote_args = ['%action', '%s']
310 remote_action = "-n"
311 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000312 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000313
314
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800315class Chrome(UnixBrowser):
316 "Launcher class for Google Chrome browser."
317
318 remote_args = ['%action', '%s']
319 remote_action = ""
320 remote_action_newwin = "--new-window"
321 remote_action_newtab = ""
322 background = True
323
324Chromium = Chrome
325
326
Georg Brandle8f24432005-10-03 14:16:44 +0000327class Opera(UnixBrowser):
328 "Launcher class for Opera browser."
329
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400330 remote_args = ['%action', '%s']
Georg Brandl23929f22006-01-20 21:03:35 +0000331 remote_action = ""
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400332 remote_action_newwin = "--new-window"
333 remote_action_newtab = ""
Georg Brandl23929f22006-01-20 21:03:35 +0000334 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000335
336
337class Elinks(UnixBrowser):
338 "Launcher class for Elinks browsers."
339
Georg Brandl23929f22006-01-20 21:03:35 +0000340 remote_args = ['-remote', 'openURL(%s%action)']
341 remote_action = ""
342 remote_action_newwin = ",new-window"
343 remote_action_newtab = ",new-tab"
344 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000345
Georg Brandl23929f22006-01-20 21:03:35 +0000346 # elinks doesn't like its stdout to be redirected -
347 # it uses redirected stdout as a signal to do -dump
348 redirect_stdout = False
349
350
351class Konqueror(BaseBrowser):
352 """Controller for the KDE File Manager (kfm, or Konqueror).
353
354 See the output of ``kfmclient --commands``
355 for more information on the Konqueror remote-control interface.
356 """
357
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000358 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700359 sys.audit("webbrowser.open", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000360 # XXX Currently I know no way to prevent KFM from opening a new win.
361 if new == 2:
362 action = "newTab"
363 else:
364 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000365
R David Murray02ca1442012-09-03 12:44:29 -0400366 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000367
Georg Brandl23929f22006-01-20 21:03:35 +0000368 try:
369 p = subprocess.Popen(["kfmclient", action, url],
370 close_fds=True, stdin=devnull,
371 stdout=devnull, stderr=devnull)
372 except OSError:
373 # fall through to next variant
374 pass
375 else:
376 p.wait()
377 # kfmclient's return code unfortunately has no meaning as it seems
378 return True
379
380 try:
381 p = subprocess.Popen(["konqueror", "--silent", url],
382 close_fds=True, stdin=devnull,
383 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700384 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000385 except OSError:
386 # fall through to next variant
387 pass
388 else:
389 if p.poll() is None:
390 # Should be running now.
391 return True
Tim Peters887c0802006-01-20 23:40:56 +0000392
Georg Brandl23929f22006-01-20 21:03:35 +0000393 try:
394 p = subprocess.Popen(["kfm", "-d", url],
395 close_fds=True, stdin=devnull,
396 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700397 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000398 except OSError:
399 return False
400 else:
401 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000402
403
404class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000405 # There should be a way to maintain a connection to Grail, but the
406 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000407 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000408 def _find_grail_rc(self):
409 import glob
410 import pwd
411 import socket
412 import tempfile
413 tempdir = os.path.join(tempfile.gettempdir(),
414 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000415 user = pwd.getpwuid(os.getuid())[0]
Serhiy Storchaka93558682020-06-20 11:10:31 +0300416 filename = os.path.join(glob.escape(tempdir), glob.escape(user) + "-*")
Fred Drake3f8f1642001-07-19 03:46:26 +0000417 maybes = glob.glob(filename)
418 if not maybes:
419 return None
420 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
421 for fn in maybes:
422 # need to PING each one until we find one that's live
423 try:
424 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200425 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000426 # no good; attempt to clean it out, but don't fail:
427 try:
428 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200429 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000430 pass
431 else:
432 return s
433
434 def _remote(self, action):
435 s = self._find_grail_rc()
436 if not s:
437 return 0
438 s.send(action)
439 s.close()
440 return 1
441
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000442 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700443 sys.audit("webbrowser.open", url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000444 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000445 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000446 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000447 ok = self._remote("LOAD " + url)
448 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000449
Fred Drakec70b4482000-07-09 16:45:56 +0000450
Tim Peters658cba62001-02-09 20:06:00 +0000451#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000452# Platform support for Unix
453#
Fred Drakec70b4482000-07-09 16:45:56 +0000454
Georg Brandle8f24432005-10-03 14:16:44 +0000455# These are the right tests because all these Unix browsers require either
456# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000457
Neal Norwitz196f7332005-10-04 03:17:49 +0000458def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000459
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200460 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200461 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200462 register("xdg-open", None, BackgroundBrowser("xdg-open"))
463
464 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200465 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200466 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
467
Guido van Rossumd8faa362007-04-27 19:54:29 +0000468 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200469 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000470 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000471
Guido van Rossumd8faa362007-04-27 19:54:29 +0000472 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200473 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000474 register("kfmclient", Konqueror, Konqueror("kfmclient"))
475
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100476 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100477 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
478
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200479 # The Mozilla browsers
480 for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200481 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000482 register(browser, None, Mozilla(browser))
483
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200484 # The Netscape and old Mozilla browsers
485 for browser in ("mozilla-firefox",
486 "mozilla-firebird", "firebird",
487 "mozilla", "netscape"):
488 if shutil.which(browser):
489 register(browser, None, Netscape(browser))
490
Georg Brandle8f24432005-10-03 14:16:44 +0000491 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200492 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000493 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200494 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000495 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000496
Georg Brandle8f24432005-10-03 14:16:44 +0000497 # Gnome's Galeon and Epiphany
498 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200499 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000500 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000501
Georg Brandle8f24432005-10-03 14:16:44 +0000502 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200503 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000504 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000505
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800506 # Google Chrome/Chromium browsers
507 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200508 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800509 register(browser, None, Chrome(browser))
510
Georg Brandle8f24432005-10-03 14:16:44 +0000511 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200512 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000513 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000514
Georg Brandle8f24432005-10-03 14:16:44 +0000515 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200516 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000517 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000518
Georg Brandle8f24432005-10-03 14:16:44 +0000519 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200520 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000521 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000522
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200523def register_standard_browsers():
524 global _tryorder
525 _tryorder = []
526
527 if sys.platform == 'darwin':
528 register("MacOSX", None, MacOSXOSAScript('default'))
529 register("chrome", None, MacOSXOSAScript('chrome'))
530 register("firefox", None, MacOSXOSAScript('firefox'))
531 register("safari", None, MacOSXOSAScript('safari'))
532 # OS X can use below Unix support (but we prefer using the OS X
533 # specific stuff)
534
Miss Islington (bot)fbefdaf2021-05-06 12:49:26 -0700535 if sys.platform == "serenityos":
536 # SerenityOS webbrowser, simply called "Browser".
537 register("Browser", None, BackgroundBrowser("Browser"))
538
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200539 if sys.platform[:3] == "win":
540 # First try to use the default Windows browser
541 register("windows-default", WindowsDefault)
542
543 # Detect some common Windows browsers, fallback to IE
544 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
545 "Internet Explorer\\IEXPLORE.EXE")
546 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
547 "netscape", "opera", iexplore):
548 if shutil.which(browser):
549 register(browser, None, BackgroundBrowser(browser))
David Steelee3ce6952017-02-24 23:47:38 -0500550 else:
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200551 # Prefer X browsers if present
Jeremy Attalic822efe2020-06-03 08:42:33 -0400552 if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200553 try:
554 cmd = "xdg-settings get default-web-browser".split()
555 raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
556 result = raw_result.decode().strip()
Ronald Oussoren23831a72020-11-08 10:46:55 +0100557 except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) :
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200558 pass
559 else:
560 global _os_preferred_browser
561 _os_preferred_browser = result
David Steelee3ce6952017-02-24 23:47:38 -0500562
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200563 register_X_browsers()
Neal Norwitz196f7332005-10-04 03:17:49 +0000564
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200565 # Also try console browsers
566 if os.environ.get("TERM"):
567 if shutil.which("www-browser"):
568 register("www-browser", None, GenericBrowser("www-browser"))
569 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
570 if shutil.which("links"):
571 register("links", None, GenericBrowser("links"))
572 if shutil.which("elinks"):
573 register("elinks", None, Elinks("elinks"))
574 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
575 if shutil.which("lynx"):
576 register("lynx", None, GenericBrowser("lynx"))
577 # The w3m browser <http://w3m.sourceforge.net/>
578 if shutil.which("w3m"):
579 register("w3m", None, GenericBrowser("w3m"))
580
581 # OK, now that we know what the default preference orders for each
582 # platform are, allow user to override them with the BROWSER variable.
583 if "BROWSER" in os.environ:
584 userchoices = os.environ["BROWSER"].split(os.pathsep)
585 userchoices.reverse()
586
587 # Treat choices in same way as if passed into get() but do register
588 # and prepend to _tryorder
589 for cmdline in userchoices:
590 if cmdline != '':
Zhiming Wang8c281ed2018-11-26 16:29:45 -0500591 cmd = _synthesize(cmdline, preferred=True)
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200592 if cmd[1] is None:
593 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
594
595 # what to do if _tryorder is now empty?
596
Fred Drake3f8f1642001-07-19 03:46:26 +0000597
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000598#
599# Platform support for Windows
600#
Fred Drakec70b4482000-07-09 16:45:56 +0000601
602if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000603 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000604 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700605 sys.audit("webbrowser.open", url)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000606 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700607 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200608 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000609 # [Error 22] No application is associated with the specified
610 # file for this operation: '<URL>'
611 return False
612 else:
613 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000614
Fred Drakec70b4482000-07-09 16:45:56 +0000615#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000616# Platform support for MacOS
617#
Fred Drakec70b4482000-07-09 16:45:56 +0000618
Georg Brandle8f24432005-10-03 14:16:44 +0000619if sys.platform == 'darwin':
620 # Adapted from patch submitted to SourceForge by Steven J. Burr
621 class MacOSX(BaseBrowser):
622 """Launcher class for Aqua browsers on Mac OS X
623
624 Optionally specify a browser name on instantiation. Note that this
625 will not work for Aqua browsers if the user has moved the application
626 package after installation.
627
628 If no browser is specified, the default browser, as specified in the
629 Internet System Preferences panel, will be used.
630 """
631 def __init__(self, name):
632 self.name = name
633
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000634 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700635 sys.audit("webbrowser.open", url)
Georg Brandle8f24432005-10-03 14:16:44 +0000636 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000637 # hack for local urls
638 if not ':' in url:
639 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000640
Georg Brandle8f24432005-10-03 14:16:44 +0000641 # new must be 0 or 1
642 new = int(bool(new))
643 if self.name == "default":
644 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000645 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000646 else:
647 # User called get and chose a browser
648 if self.name == "OmniWeb":
649 toWindow = ""
650 else:
651 # Include toWindow parameter of OpenURL command for browsers
652 # that support it. 0 == new window; -1 == existing
653 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000654 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000655 script = '''tell application "%s"
656 activate
657 %s %s
658 end tell''' % (self.name, cmd, toWindow)
659 # Open pipe to AppleScript through osascript command
660 osapipe = os.popen("osascript", "w")
661 if osapipe is None:
662 return False
663 # Write script to osascript's stdin
664 osapipe.write(script)
665 rc = osapipe.close()
666 return not rc
667
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000668 class MacOSXOSAScript(BaseBrowser):
669 def __init__(self, name):
670 self._name = name
671
672 def open(self, url, new=0, autoraise=True):
673 if self._name == 'default':
674 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
675 else:
676 script = '''
677 tell application "%s"
678 activate
679 open location "%s"
680 end
681 '''%(self._name, url.replace('"', '%22'))
682
683 osapipe = os.popen("osascript", "w")
684 if osapipe is None:
685 return False
686
687 osapipe.write(script)
688 rc = osapipe.close()
689 return not rc
690
691
Georg Brandle8f24432005-10-03 14:16:44 +0000692def main():
693 import getopt
694 usage = """Usage: %s [-n | -t] url
695 -n: open new window
696 -t: open new tab""" % sys.argv[0]
697 try:
698 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000699 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000700 print(msg, file=sys.stderr)
701 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000702 sys.exit(1)
703 new_win = 0
704 for o, a in opts:
705 if o == '-n': new_win = 1
706 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000707 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000708 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000709 sys.exit(1)
710
711 url = args[0]
712 open(url, new_win)
713
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000714 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000715
Georg Brandle8f24432005-10-03 14:16:44 +0000716if __name__ == "__main__":
717 main()