blob: 6023c1e13841d2b21937bb285127df69ab2bd4c2 [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +00002"""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
535 if sys.platform[:3] == "win":
536 # First try to use the default Windows browser
537 register("windows-default", WindowsDefault)
538
539 # Detect some common Windows browsers, fallback to IE
540 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
541 "Internet Explorer\\IEXPLORE.EXE")
542 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
543 "netscape", "opera", iexplore):
544 if shutil.which(browser):
545 register(browser, None, BackgroundBrowser(browser))
David Steelee3ce6952017-02-24 23:47:38 -0500546 else:
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200547 # Prefer X browsers if present
Jeremy Attalic822efe2020-06-03 08:42:33 -0400548 if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200549 try:
550 cmd = "xdg-settings get default-web-browser".split()
551 raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
552 result = raw_result.decode().strip()
Ronald Oussoren23831a72020-11-08 10:46:55 +0100553 except (FileNotFoundError, subprocess.CalledProcessError, PermissionError, NotADirectoryError) :
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200554 pass
555 else:
556 global _os_preferred_browser
557 _os_preferred_browser = result
David Steelee3ce6952017-02-24 23:47:38 -0500558
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200559 register_X_browsers()
Neal Norwitz196f7332005-10-04 03:17:49 +0000560
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200561 # Also try console browsers
562 if os.environ.get("TERM"):
563 if shutil.which("www-browser"):
564 register("www-browser", None, GenericBrowser("www-browser"))
565 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
566 if shutil.which("links"):
567 register("links", None, GenericBrowser("links"))
568 if shutil.which("elinks"):
569 register("elinks", None, Elinks("elinks"))
570 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
571 if shutil.which("lynx"):
572 register("lynx", None, GenericBrowser("lynx"))
573 # The w3m browser <http://w3m.sourceforge.net/>
574 if shutil.which("w3m"):
575 register("w3m", None, GenericBrowser("w3m"))
576
577 # OK, now that we know what the default preference orders for each
578 # platform are, allow user to override them with the BROWSER variable.
579 if "BROWSER" in os.environ:
580 userchoices = os.environ["BROWSER"].split(os.pathsep)
581 userchoices.reverse()
582
583 # Treat choices in same way as if passed into get() but do register
584 # and prepend to _tryorder
585 for cmdline in userchoices:
586 if cmdline != '':
Zhiming Wang8c281ed2018-11-26 16:29:45 -0500587 cmd = _synthesize(cmdline, preferred=True)
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200588 if cmd[1] is None:
589 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
590
591 # what to do if _tryorder is now empty?
592
Fred Drake3f8f1642001-07-19 03:46:26 +0000593
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000594#
595# Platform support for Windows
596#
Fred Drakec70b4482000-07-09 16:45:56 +0000597
598if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000599 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000600 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700601 sys.audit("webbrowser.open", url)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000602 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700603 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200604 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000605 # [Error 22] No application is associated with the specified
606 # file for this operation: '<URL>'
607 return False
608 else:
609 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000610
Fred Drakec70b4482000-07-09 16:45:56 +0000611#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000612# Platform support for MacOS
613#
Fred Drakec70b4482000-07-09 16:45:56 +0000614
Georg Brandle8f24432005-10-03 14:16:44 +0000615if sys.platform == 'darwin':
616 # Adapted from patch submitted to SourceForge by Steven J. Burr
617 class MacOSX(BaseBrowser):
618 """Launcher class for Aqua browsers on Mac OS X
619
620 Optionally specify a browser name on instantiation. Note that this
621 will not work for Aqua browsers if the user has moved the application
622 package after installation.
623
624 If no browser is specified, the default browser, as specified in the
625 Internet System Preferences panel, will be used.
626 """
627 def __init__(self, name):
628 self.name = name
629
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000630 def open(self, url, new=0, autoraise=True):
Steve Dower60419a72019-06-24 08:42:54 -0700631 sys.audit("webbrowser.open", url)
Georg Brandle8f24432005-10-03 14:16:44 +0000632 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000633 # hack for local urls
634 if not ':' in url:
635 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000636
Georg Brandle8f24432005-10-03 14:16:44 +0000637 # new must be 0 or 1
638 new = int(bool(new))
639 if self.name == "default":
640 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000641 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000642 else:
643 # User called get and chose a browser
644 if self.name == "OmniWeb":
645 toWindow = ""
646 else:
647 # Include toWindow parameter of OpenURL command for browsers
648 # that support it. 0 == new window; -1 == existing
649 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000650 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000651 script = '''tell application "%s"
652 activate
653 %s %s
654 end tell''' % (self.name, cmd, toWindow)
655 # Open pipe to AppleScript through osascript command
656 osapipe = os.popen("osascript", "w")
657 if osapipe is None:
658 return False
659 # Write script to osascript's stdin
660 osapipe.write(script)
661 rc = osapipe.close()
662 return not rc
663
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000664 class MacOSXOSAScript(BaseBrowser):
665 def __init__(self, name):
666 self._name = name
667
668 def open(self, url, new=0, autoraise=True):
669 if self._name == 'default':
670 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
671 else:
672 script = '''
673 tell application "%s"
674 activate
675 open location "%s"
676 end
677 '''%(self._name, url.replace('"', '%22'))
678
679 osapipe = os.popen("osascript", "w")
680 if osapipe is None:
681 return False
682
683 osapipe.write(script)
684 rc = osapipe.close()
685 return not rc
686
687
Georg Brandle8f24432005-10-03 14:16:44 +0000688def main():
689 import getopt
690 usage = """Usage: %s [-n | -t] url
691 -n: open new window
692 -t: open new tab""" % sys.argv[0]
693 try:
694 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000695 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000696 print(msg, file=sys.stderr)
697 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000698 sys.exit(1)
699 new_win = 0
700 for o, a in opts:
701 if o == '-n': new_win = 1
702 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000703 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000704 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000705 sys.exit(1)
706
707 url = args[0]
708 open(url, new_win)
709
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000710 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000711
Georg Brandle8f24432005-10-03 14:16:44 +0000712if __name__ == "__main__":
713 main()