blob: 0af36c4301d798a4b7c09bcdc1858556365ea1b2 [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):
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020072 if _tryorder is None:
73 with _lock:
74 if _tryorder is None:
75 register_standard_browsers()
Georg Brandle8f24432005-10-03 14:16:44 +000076 for name in _tryorder:
77 browser = get(name)
78 if browser.open(url, new, autoraise):
79 return True
80 return False
Fred Drakec70b4482000-07-09 16:45:56 +000081
Fred Drake3f8f1642001-07-19 03:46:26 +000082def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000083 return open(url, 1)
84
85def open_new_tab(url):
86 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000087
Fred Drakef4e5bd92001-04-12 22:07:27 +000088
Zhiming Wang8c281ed2018-11-26 16:29:45 -050089def _synthesize(browser, *, preferred=False):
Fred Drakef4e5bd92001-04-12 22:07:27 +000090 """Attempt to synthesize a controller base on existing controllers.
91
92 This is useful to create a controller when a user specifies a path to
93 an entry in the BROWSER environment variable -- we can copy a general
94 controller to operate using a specific installation of the desired
95 browser in this way.
96
97 If we can't create a controller in this way, or if there is no
98 executable for the requested browser, return [None, None].
99
100 """
Georg Brandle8f24432005-10-03 14:16:44 +0000101 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200102 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000103 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +0000104 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000105 try:
106 command = _browsers[name.lower()]
107 except KeyError:
108 return [None, None]
109 # now attempt to clone to fit the new name:
110 controller = command[1]
111 if controller and name.lower() == controller.basename:
112 import copy
113 controller = copy.copy(controller)
114 controller.name = browser
115 controller.basename = os.path.basename(browser)
Serhiy Storchaka25b804a2018-07-08 10:22:32 +0300116 register(browser, None, instance=controller, preferred=preferred)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000117 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000118 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000119
Fred Drake3f8f1642001-07-19 03:46:26 +0000120
Georg Brandle8f24432005-10-03 14:16:44 +0000121# General parent classes
122
123class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000124 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000125
Georg Brandl23929f22006-01-20 21:03:35 +0000126 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000127
Georg Brandle8f24432005-10-03 14:16:44 +0000128 def __init__(self, name=""):
129 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000130 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000131
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000132 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000133 raise NotImplementedError
134
Georg Brandle8f24432005-10-03 14:16:44 +0000135 def open_new(self, url):
136 return self.open(url, 1)
137
138 def open_new_tab(self, url):
139 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000140
141
Georg Brandle8f24432005-10-03 14:16:44 +0000142class GenericBrowser(BaseBrowser):
143 """Class for all browsers started with a command
144 and without remote functionality."""
145
Georg Brandl23929f22006-01-20 21:03:35 +0000146 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000147 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000148 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000149 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000150 else:
151 # name should be a list with arguments
152 self.name = name[0]
153 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000154 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000155
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000156 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700157 sys.audit("webbrowser.open", url)
Tim Peters887c0802006-01-20 23:40:56 +0000158 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000159 for arg in self.args]
160 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000161 if sys.platform[:3] == 'win':
162 p = subprocess.Popen(cmdline)
163 else:
164 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000165 return not p.wait()
166 except OSError:
167 return False
168
169
170class BackgroundBrowser(GenericBrowser):
171 """Class for all browsers which are to be started in the
172 background."""
173
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000174 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000175 cmdline = [self.name] + [arg.replace("%s", url)
176 for arg in self.args]
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700177 sys.audit("webbrowser.open", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000178 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000179 if sys.platform[:3] == 'win':
180 p = subprocess.Popen(cmdline)
181 else:
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700182 p = subprocess.Popen(cmdline, close_fds=True,
183 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000184 return (p.poll() is None)
185 except OSError:
186 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000187
188
Georg Brandle8f24432005-10-03 14:16:44 +0000189class UnixBrowser(BaseBrowser):
190 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000191
Georg Brandle8f24432005-10-03 14:16:44 +0000192 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400193 background = False
194 redirect_stdout = True
195 # In remote_args, %s will be replaced with the requested URL. %action will
196 # be replaced depending on the value of 'new' passed to open.
197 # remote_action is used for new=0 (open). If newwin is not None, it is
198 # used for new=1 (open_new). If newtab is not None, it is used for
199 # new=3 (open_new_tab). After both substitutions are made, any empty
200 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000201 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000202 remote_action = None
203 remote_action_newwin = None
204 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000205
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700206 def _invoke(self, args, remote, autoraise, url=None):
Georg Brandl23929f22006-01-20 21:03:35 +0000207 raise_opt = []
208 if remote and self.raise_opts:
209 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000210 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000211 opt = self.raise_opts[autoraise]
212 if opt: raise_opt = [opt]
213
214 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000215
Georg Brandl23929f22006-01-20 21:03:35 +0000216 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400217 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000218 else:
219 # for TTY browsers, we need stdin/out
220 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000221 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
222 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400223 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000224 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200225 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000226 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200227 try:
228 rc = p.wait(5)
229 # if remote call failed, open() will try direct invocation
230 return not rc
231 except subprocess.TimeoutExpired:
232 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000233 elif self.background:
234 if p.poll() is None:
235 return True
236 else:
237 return False
238 else:
239 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000240
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000241 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700242 sys.audit("webbrowser.open", url)
Georg Brandle8f24432005-10-03 14:16:44 +0000243 if new == 0:
244 action = self.remote_action
245 elif new == 1:
246 action = self.remote_action_newwin
247 elif new == 2:
248 if self.remote_action_newtab is None:
249 action = self.remote_action_newwin
250 else:
251 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000252 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000253 raise Error("Bad 'new' parameter to open(); " +
254 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000255
Georg Brandl23929f22006-01-20 21:03:35 +0000256 args = [arg.replace("%s", url).replace("%action", action)
257 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400258 args = [arg for arg in args if arg]
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700259 success = self._invoke(args, True, autoraise, url)
Georg Brandl23929f22006-01-20 21:03:35 +0000260 if not success:
261 # remote invocation failed, try straight way
262 args = [arg.replace("%s", url) for arg in self.args]
263 return self._invoke(args, False, False)
264 else:
265 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000266
267
Georg Brandle8f24432005-10-03 14:16:44 +0000268class Mozilla(UnixBrowser):
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200269 """Launcher class for Mozilla browsers."""
270
271 remote_args = ['%action', '%s']
272 remote_action = ""
273 remote_action_newwin = "-new-window"
274 remote_action_newtab = "-new-tab"
275 background = True
276
277
278class Netscape(UnixBrowser):
279 """Launcher class for Netscape browser."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000280
Georg Brandl23929f22006-01-20 21:03:35 +0000281 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000282 remote_args = ['-remote', 'openURL(%s%action)']
283 remote_action = ""
284 remote_action_newwin = ",new-window"
285 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000286 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000287
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000288
Georg Brandle8f24432005-10-03 14:16:44 +0000289class Galeon(UnixBrowser):
290 """Launcher class for Galeon/Epiphany browsers."""
291
Georg Brandl23929f22006-01-20 21:03:35 +0000292 raise_opts = ["-noraise", ""]
293 remote_args = ['%action', '%s']
294 remote_action = "-n"
295 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000296 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000297
298
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800299class Chrome(UnixBrowser):
300 "Launcher class for Google Chrome browser."
301
302 remote_args = ['%action', '%s']
303 remote_action = ""
304 remote_action_newwin = "--new-window"
305 remote_action_newtab = ""
306 background = True
307
308Chromium = Chrome
309
310
Georg Brandle8f24432005-10-03 14:16:44 +0000311class Opera(UnixBrowser):
312 "Launcher class for Opera browser."
313
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400314 remote_args = ['%action', '%s']
Georg Brandl23929f22006-01-20 21:03:35 +0000315 remote_action = ""
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400316 remote_action_newwin = "--new-window"
317 remote_action_newtab = ""
Georg Brandl23929f22006-01-20 21:03:35 +0000318 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000319
320
321class Elinks(UnixBrowser):
322 "Launcher class for Elinks browsers."
323
Georg Brandl23929f22006-01-20 21:03:35 +0000324 remote_args = ['-remote', 'openURL(%s%action)']
325 remote_action = ""
326 remote_action_newwin = ",new-window"
327 remote_action_newtab = ",new-tab"
328 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000329
Georg Brandl23929f22006-01-20 21:03:35 +0000330 # elinks doesn't like its stdout to be redirected -
331 # it uses redirected stdout as a signal to do -dump
332 redirect_stdout = False
333
334
335class Konqueror(BaseBrowser):
336 """Controller for the KDE File Manager (kfm, or Konqueror).
337
338 See the output of ``kfmclient --commands``
339 for more information on the Konqueror remote-control interface.
340 """
341
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000342 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700343 sys.audit("webbrowser.open", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000344 # XXX Currently I know no way to prevent KFM from opening a new win.
345 if new == 2:
346 action = "newTab"
347 else:
348 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000349
R David Murray02ca1442012-09-03 12:44:29 -0400350 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000351
Georg Brandl23929f22006-01-20 21:03:35 +0000352 try:
353 p = subprocess.Popen(["kfmclient", action, url],
354 close_fds=True, stdin=devnull,
355 stdout=devnull, stderr=devnull)
356 except OSError:
357 # fall through to next variant
358 pass
359 else:
360 p.wait()
361 # kfmclient's return code unfortunately has no meaning as it seems
362 return True
363
364 try:
365 p = subprocess.Popen(["konqueror", "--silent", url],
366 close_fds=True, stdin=devnull,
367 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700368 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000369 except OSError:
370 # fall through to next variant
371 pass
372 else:
373 if p.poll() is None:
374 # Should be running now.
375 return True
Tim Peters887c0802006-01-20 23:40:56 +0000376
Georg Brandl23929f22006-01-20 21:03:35 +0000377 try:
378 p = subprocess.Popen(["kfm", "-d", url],
379 close_fds=True, stdin=devnull,
380 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700381 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000382 except OSError:
383 return False
384 else:
385 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000386
387
388class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000389 # There should be a way to maintain a connection to Grail, but the
390 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000391 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000392 def _find_grail_rc(self):
393 import glob
394 import pwd
395 import socket
396 import tempfile
397 tempdir = os.path.join(tempfile.gettempdir(),
398 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000399 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000400 filename = os.path.join(tempdir, user + "-*")
401 maybes = glob.glob(filename)
402 if not maybes:
403 return None
404 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
405 for fn in maybes:
406 # need to PING each one until we find one that's live
407 try:
408 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200409 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000410 # no good; attempt to clean it out, but don't fail:
411 try:
412 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200413 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000414 pass
415 else:
416 return s
417
418 def _remote(self, action):
419 s = self._find_grail_rc()
420 if not s:
421 return 0
422 s.send(action)
423 s.close()
424 return 1
425
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000426 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700427 sys.audit("webbrowser.open", url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000428 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000429 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000430 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000431 ok = self._remote("LOAD " + url)
432 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000433
Fred Drakec70b4482000-07-09 16:45:56 +0000434
Tim Peters658cba62001-02-09 20:06:00 +0000435#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000436# Platform support for Unix
437#
Fred Drakec70b4482000-07-09 16:45:56 +0000438
Georg Brandle8f24432005-10-03 14:16:44 +0000439# These are the right tests because all these Unix browsers require either
440# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000441
Neal Norwitz196f7332005-10-04 03:17:49 +0000442def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000443
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200444 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200445 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200446 register("xdg-open", None, BackgroundBrowser("xdg-open"))
447
448 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200449 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200450 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
451
Guido van Rossumd8faa362007-04-27 19:54:29 +0000452 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200453 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000454 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000455
Guido van Rossumd8faa362007-04-27 19:54:29 +0000456 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200457 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000458 register("kfmclient", Konqueror, Konqueror("kfmclient"))
459
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100460 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100461 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
462
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200463 # The Mozilla browsers
464 for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200465 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000466 register(browser, None, Mozilla(browser))
467
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200468 # The Netscape and old Mozilla browsers
469 for browser in ("mozilla-firefox",
470 "mozilla-firebird", "firebird",
471 "mozilla", "netscape"):
472 if shutil.which(browser):
473 register(browser, None, Netscape(browser))
474
Georg Brandle8f24432005-10-03 14:16:44 +0000475 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200476 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000477 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200478 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000479 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000480
Georg Brandle8f24432005-10-03 14:16:44 +0000481 # Gnome's Galeon and Epiphany
482 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200483 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000484 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000485
Georg Brandle8f24432005-10-03 14:16:44 +0000486 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200487 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000488 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000489
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800490 # Google Chrome/Chromium browsers
491 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200492 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800493 register(browser, None, Chrome(browser))
494
Georg Brandle8f24432005-10-03 14:16:44 +0000495 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200496 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000497 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000498
Georg Brandle8f24432005-10-03 14:16:44 +0000499 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200500 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000501 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000502
Georg Brandle8f24432005-10-03 14:16:44 +0000503 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200504 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000505 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000506
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200507def register_standard_browsers():
508 global _tryorder
509 _tryorder = []
510
511 if sys.platform == 'darwin':
512 register("MacOSX", None, MacOSXOSAScript('default'))
513 register("chrome", None, MacOSXOSAScript('chrome'))
514 register("firefox", None, MacOSXOSAScript('firefox'))
515 register("safari", None, MacOSXOSAScript('safari'))
516 # OS X can use below Unix support (but we prefer using the OS X
517 # specific stuff)
518
519 if sys.platform[:3] == "win":
520 # First try to use the default Windows browser
521 register("windows-default", WindowsDefault)
522
523 # Detect some common Windows browsers, fallback to IE
524 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
525 "Internet Explorer\\IEXPLORE.EXE")
526 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
527 "netscape", "opera", iexplore):
528 if shutil.which(browser):
529 register(browser, None, BackgroundBrowser(browser))
David Steelee3ce6952017-02-24 23:47:38 -0500530 else:
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200531 # Prefer X browsers if present
532 if os.environ.get("DISPLAY"):
533 try:
534 cmd = "xdg-settings get default-web-browser".split()
535 raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
536 result = raw_result.decode().strip()
537 except (FileNotFoundError, subprocess.CalledProcessError):
538 pass
539 else:
540 global _os_preferred_browser
541 _os_preferred_browser = result
David Steelee3ce6952017-02-24 23:47:38 -0500542
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200543 register_X_browsers()
Neal Norwitz196f7332005-10-04 03:17:49 +0000544
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200545 # Also try console browsers
546 if os.environ.get("TERM"):
547 if shutil.which("www-browser"):
548 register("www-browser", None, GenericBrowser("www-browser"))
549 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
550 if shutil.which("links"):
551 register("links", None, GenericBrowser("links"))
552 if shutil.which("elinks"):
553 register("elinks", None, Elinks("elinks"))
554 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
555 if shutil.which("lynx"):
556 register("lynx", None, GenericBrowser("lynx"))
557 # The w3m browser <http://w3m.sourceforge.net/>
558 if shutil.which("w3m"):
559 register("w3m", None, GenericBrowser("w3m"))
560
561 # OK, now that we know what the default preference orders for each
562 # platform are, allow user to override them with the BROWSER variable.
563 if "BROWSER" in os.environ:
564 userchoices = os.environ["BROWSER"].split(os.pathsep)
565 userchoices.reverse()
566
567 # Treat choices in same way as if passed into get() but do register
568 # and prepend to _tryorder
569 for cmdline in userchoices:
570 if cmdline != '':
Zhiming Wang8c281ed2018-11-26 16:29:45 -0500571 cmd = _synthesize(cmdline, preferred=True)
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200572 if cmd[1] is None:
573 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
574
575 # what to do if _tryorder is now empty?
576
Fred Drake3f8f1642001-07-19 03:46:26 +0000577
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000578#
579# Platform support for Windows
580#
Fred Drakec70b4482000-07-09 16:45:56 +0000581
582if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000583 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000584 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700585 sys.audit("webbrowser.open", url)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000586 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700587 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200588 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000589 # [Error 22] No application is associated with the specified
590 # file for this operation: '<URL>'
591 return False
592 else:
593 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000594
Fred Drakec70b4482000-07-09 16:45:56 +0000595#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000596# Platform support for MacOS
597#
Fred Drakec70b4482000-07-09 16:45:56 +0000598
Georg Brandle8f24432005-10-03 14:16:44 +0000599if sys.platform == 'darwin':
600 # Adapted from patch submitted to SourceForge by Steven J. Burr
601 class MacOSX(BaseBrowser):
602 """Launcher class for Aqua browsers on Mac OS X
603
604 Optionally specify a browser name on instantiation. Note that this
605 will not work for Aqua browsers if the user has moved the application
606 package after installation.
607
608 If no browser is specified, the default browser, as specified in the
609 Internet System Preferences panel, will be used.
610 """
611 def __init__(self, name):
612 self.name = name
613
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000614 def open(self, url, new=0, autoraise=True):
Miss Islington (bot)8763d432019-06-24 09:09:47 -0700615 sys.audit("webbrowser.open", url)
Georg Brandle8f24432005-10-03 14:16:44 +0000616 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000617 # hack for local urls
618 if not ':' in url:
619 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000620
Georg Brandle8f24432005-10-03 14:16:44 +0000621 # new must be 0 or 1
622 new = int(bool(new))
623 if self.name == "default":
624 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000625 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000626 else:
627 # User called get and chose a browser
628 if self.name == "OmniWeb":
629 toWindow = ""
630 else:
631 # Include toWindow parameter of OpenURL command for browsers
632 # that support it. 0 == new window; -1 == existing
633 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000634 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000635 script = '''tell application "%s"
636 activate
637 %s %s
638 end tell''' % (self.name, cmd, toWindow)
639 # Open pipe to AppleScript through osascript command
640 osapipe = os.popen("osascript", "w")
641 if osapipe is None:
642 return False
643 # Write script to osascript's stdin
644 osapipe.write(script)
645 rc = osapipe.close()
646 return not rc
647
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000648 class MacOSXOSAScript(BaseBrowser):
649 def __init__(self, name):
650 self._name = name
651
652 def open(self, url, new=0, autoraise=True):
653 if self._name == 'default':
654 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
655 else:
656 script = '''
657 tell application "%s"
658 activate
659 open location "%s"
660 end
661 '''%(self._name, url.replace('"', '%22'))
662
663 osapipe = os.popen("osascript", "w")
664 if osapipe is None:
665 return False
666
667 osapipe.write(script)
668 rc = osapipe.close()
669 return not rc
670
671
Georg Brandle8f24432005-10-03 14:16:44 +0000672def main():
673 import getopt
674 usage = """Usage: %s [-n | -t] url
675 -n: open new window
676 -t: open new tab""" % sys.argv[0]
677 try:
678 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000679 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000680 print(msg, file=sys.stderr)
681 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000682 sys.exit(1)
683 new_win = 0
684 for o, a in opts:
685 if o == '-n': new_win = 1
686 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000687 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000688 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000689 sys.exit(1)
690
691 url = args[0]
692 open(url, new_win)
693
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000694 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000695
Georg Brandle8f24432005-10-03 14:16:44 +0000696if __name__ == "__main__":
697 main()