blob: a9eac69650546fe91c06e2dc1e613d6891c2e583 [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
Fred Drakec70b4482000-07-09 16:45:56 +000010
Georg Brandle8f24432005-10-03 14:16:44 +000011__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
Skip Montanaro40fc1602001-03-01 04:27:19 +000012
Fred Drakec70b4482000-07-09 16:45:56 +000013class Error(Exception):
14 pass
15
David Steelee3ce6952017-02-24 23:47:38 -050016_browsers = {} # Dictionary of available browser controllers
17_tryorder = [] # Preference order of available browsers
18_os_preferred_browser = None # The preferred browser
Fred Drakec70b4482000-07-09 16:45:56 +000019
David Steelee3ce6952017-02-24 23:47:38 -050020def register(name, klass, instance=None, *, preferred=False):
21 """Register a browser connector."""
Fred Drakec70b4482000-07-09 16:45:56 +000022 _browsers[name.lower()] = [klass, instance]
David Steelee3ce6952017-02-24 23:47:38 -050023
24 # Preferred browsers go to the front of the list.
25 # Need to match to the default browser returned by xdg-settings, which
26 # may be of the form e.g. "firefox.desktop".
27 if preferred or (_os_preferred_browser and name in _os_preferred_browser):
Georg Brandle8f24432005-10-03 14:16:44 +000028 _tryorder.insert(0, name)
David Steelee3ce6952017-02-24 23:47:38 -050029 else:
30 _tryorder.append(name)
Fred Drakec70b4482000-07-09 16:45:56 +000031
Eric S. Raymondf7f18512001-01-23 13:16:32 +000032def get(using=None):
33 """Return a browser launcher instance appropriate for the environment."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000034 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000035 alternatives = [using]
36 else:
37 alternatives = _tryorder
38 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000039 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000040 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000041 browser = shlex.split(browser)
42 if browser[-1] == '&':
43 return BackgroundBrowser(browser[:-1])
44 else:
45 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000046 else:
Georg Brandle8f24432005-10-03 14:16:44 +000047 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000048 try:
49 command = _browsers[browser.lower()]
50 except KeyError:
51 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000052 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000053 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000054 elif command[0] is not None:
55 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000056 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000057
58# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000059# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
60# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000061
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000062def open(url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +000063 for name in _tryorder:
64 browser = get(name)
65 if browser.open(url, new, autoraise):
66 return True
67 return False
Fred Drakec70b4482000-07-09 16:45:56 +000068
Fred Drake3f8f1642001-07-19 03:46:26 +000069def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000070 return open(url, 1)
71
72def open_new_tab(url):
73 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000074
Fred Drakef4e5bd92001-04-12 22:07:27 +000075
Georg Brandle8f24432005-10-03 14:16:44 +000076def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000077 """Attempt to synthesize a controller base on existing controllers.
78
79 This is useful to create a controller when a user specifies a path to
80 an entry in the BROWSER environment variable -- we can copy a general
81 controller to operate using a specific installation of the desired
82 browser in this way.
83
84 If we can't create a controller in this way, or if there is no
85 executable for the requested browser, return [None, None].
86
87 """
Georg Brandle8f24432005-10-03 14:16:44 +000088 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +020089 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +000090 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +000091 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +000092 try:
93 command = _browsers[name.lower()]
94 except KeyError:
95 return [None, None]
96 # now attempt to clone to fit the new name:
97 controller = command[1]
98 if controller and name.lower() == controller.basename:
99 import copy
100 controller = copy.copy(controller)
101 controller.name = browser
102 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +0000103 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000104 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000105 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000106
Fred Drake3f8f1642001-07-19 03:46:26 +0000107
Georg Brandle8f24432005-10-03 14:16:44 +0000108# General parent classes
109
110class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000111 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000112
Georg Brandl23929f22006-01-20 21:03:35 +0000113 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000114
Georg Brandle8f24432005-10-03 14:16:44 +0000115 def __init__(self, name=""):
116 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000117 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000118
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000119 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000120 raise NotImplementedError
121
Georg Brandle8f24432005-10-03 14:16:44 +0000122 def open_new(self, url):
123 return self.open(url, 1)
124
125 def open_new_tab(self, url):
126 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000127
128
Georg Brandle8f24432005-10-03 14:16:44 +0000129class GenericBrowser(BaseBrowser):
130 """Class for all browsers started with a command
131 and without remote functionality."""
132
Georg Brandl23929f22006-01-20 21:03:35 +0000133 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000134 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000135 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000136 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000137 else:
138 # name should be a list with arguments
139 self.name = name[0]
140 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000141 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000142
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000143 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000144 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000145 for arg in self.args]
146 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000147 if sys.platform[:3] == 'win':
148 p = subprocess.Popen(cmdline)
149 else:
150 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000151 return not p.wait()
152 except OSError:
153 return False
154
155
156class BackgroundBrowser(GenericBrowser):
157 """Class for all browsers which are to be started in the
158 background."""
159
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000160 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000161 cmdline = [self.name] + [arg.replace("%s", url)
162 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000163 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000164 if sys.platform[:3] == 'win':
165 p = subprocess.Popen(cmdline)
166 else:
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700167 p = subprocess.Popen(cmdline, close_fds=True,
168 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000169 return (p.poll() is None)
170 except OSError:
171 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000172
173
Georg Brandle8f24432005-10-03 14:16:44 +0000174class UnixBrowser(BaseBrowser):
175 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000176
Georg Brandle8f24432005-10-03 14:16:44 +0000177 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400178 background = False
179 redirect_stdout = True
180 # In remote_args, %s will be replaced with the requested URL. %action will
181 # be replaced depending on the value of 'new' passed to open.
182 # remote_action is used for new=0 (open). If newwin is not None, it is
183 # used for new=1 (open_new). If newtab is not None, it is used for
184 # new=3 (open_new_tab). After both substitutions are made, any empty
185 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000186 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000187 remote_action = None
188 remote_action_newwin = None
189 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000190
Georg Brandl23929f22006-01-20 21:03:35 +0000191 def _invoke(self, args, remote, autoraise):
192 raise_opt = []
193 if remote and self.raise_opts:
194 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000195 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000196 opt = self.raise_opts[autoraise]
197 if opt: raise_opt = [opt]
198
199 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000200
Georg Brandl23929f22006-01-20 21:03:35 +0000201 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400202 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000203 else:
204 # for TTY browsers, we need stdin/out
205 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000206 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
207 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400208 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000209 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200210 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000211 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200212 try:
213 rc = p.wait(5)
214 # if remote call failed, open() will try direct invocation
215 return not rc
216 except subprocess.TimeoutExpired:
217 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000218 elif self.background:
219 if p.poll() is None:
220 return True
221 else:
222 return False
223 else:
224 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000225
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000226 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000227 if new == 0:
228 action = self.remote_action
229 elif new == 1:
230 action = self.remote_action_newwin
231 elif new == 2:
232 if self.remote_action_newtab is None:
233 action = self.remote_action_newwin
234 else:
235 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000236 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000237 raise Error("Bad 'new' parameter to open(); " +
238 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000239
Georg Brandl23929f22006-01-20 21:03:35 +0000240 args = [arg.replace("%s", url).replace("%action", action)
241 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400242 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000243 success = self._invoke(args, True, autoraise)
244 if not success:
245 # remote invocation failed, try straight way
246 args = [arg.replace("%s", url) for arg in self.args]
247 return self._invoke(args, False, False)
248 else:
249 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000250
251
Georg Brandle8f24432005-10-03 14:16:44 +0000252class Mozilla(UnixBrowser):
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200253 """Launcher class for Mozilla browsers."""
254
255 remote_args = ['%action', '%s']
256 remote_action = ""
257 remote_action_newwin = "-new-window"
258 remote_action_newtab = "-new-tab"
259 background = True
260
261
262class Netscape(UnixBrowser):
263 """Launcher class for Netscape browser."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000264
Georg Brandl23929f22006-01-20 21:03:35 +0000265 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000266 remote_args = ['-remote', 'openURL(%s%action)']
267 remote_action = ""
268 remote_action_newwin = ",new-window"
269 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000270 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000271
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000272
Georg Brandle8f24432005-10-03 14:16:44 +0000273class Galeon(UnixBrowser):
274 """Launcher class for Galeon/Epiphany browsers."""
275
Georg Brandl23929f22006-01-20 21:03:35 +0000276 raise_opts = ["-noraise", ""]
277 remote_args = ['%action', '%s']
278 remote_action = "-n"
279 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000280 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000281
282
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800283class Chrome(UnixBrowser):
284 "Launcher class for Google Chrome browser."
285
286 remote_args = ['%action', '%s']
287 remote_action = ""
288 remote_action_newwin = "--new-window"
289 remote_action_newtab = ""
290 background = True
291
292Chromium = Chrome
293
294
Georg Brandle8f24432005-10-03 14:16:44 +0000295class Opera(UnixBrowser):
296 "Launcher class for Opera browser."
297
Terry Reedydad532f2010-12-28 19:30:19 +0000298 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000299 remote_args = ['-remote', 'openURL(%s%action)']
300 remote_action = ""
301 remote_action_newwin = ",new-window"
302 remote_action_newtab = ",new-page"
303 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000304
305
306class Elinks(UnixBrowser):
307 "Launcher class for Elinks browsers."
308
Georg Brandl23929f22006-01-20 21:03:35 +0000309 remote_args = ['-remote', 'openURL(%s%action)']
310 remote_action = ""
311 remote_action_newwin = ",new-window"
312 remote_action_newtab = ",new-tab"
313 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000314
Georg Brandl23929f22006-01-20 21:03:35 +0000315 # elinks doesn't like its stdout to be redirected -
316 # it uses redirected stdout as a signal to do -dump
317 redirect_stdout = False
318
319
320class Konqueror(BaseBrowser):
321 """Controller for the KDE File Manager (kfm, or Konqueror).
322
323 See the output of ``kfmclient --commands``
324 for more information on the Konqueror remote-control interface.
325 """
326
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000327 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000328 # XXX Currently I know no way to prevent KFM from opening a new win.
329 if new == 2:
330 action = "newTab"
331 else:
332 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000333
R David Murray02ca1442012-09-03 12:44:29 -0400334 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000335
Georg Brandl23929f22006-01-20 21:03:35 +0000336 try:
337 p = subprocess.Popen(["kfmclient", action, url],
338 close_fds=True, stdin=devnull,
339 stdout=devnull, stderr=devnull)
340 except OSError:
341 # fall through to next variant
342 pass
343 else:
344 p.wait()
345 # kfmclient's return code unfortunately has no meaning as it seems
346 return True
347
348 try:
349 p = subprocess.Popen(["konqueror", "--silent", url],
350 close_fds=True, stdin=devnull,
351 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700352 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000353 except OSError:
354 # fall through to next variant
355 pass
356 else:
357 if p.poll() is None:
358 # Should be running now.
359 return True
Tim Peters887c0802006-01-20 23:40:56 +0000360
Georg Brandl23929f22006-01-20 21:03:35 +0000361 try:
362 p = subprocess.Popen(["kfm", "-d", url],
363 close_fds=True, stdin=devnull,
364 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700365 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000366 except OSError:
367 return False
368 else:
369 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000370
371
372class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000373 # There should be a way to maintain a connection to Grail, but the
374 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000375 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000376 def _find_grail_rc(self):
377 import glob
378 import pwd
379 import socket
380 import tempfile
381 tempdir = os.path.join(tempfile.gettempdir(),
382 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000383 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000384 filename = os.path.join(tempdir, user + "-*")
385 maybes = glob.glob(filename)
386 if not maybes:
387 return None
388 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
389 for fn in maybes:
390 # need to PING each one until we find one that's live
391 try:
392 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200393 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000394 # no good; attempt to clean it out, but don't fail:
395 try:
396 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200397 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000398 pass
399 else:
400 return s
401
402 def _remote(self, action):
403 s = self._find_grail_rc()
404 if not s:
405 return 0
406 s.send(action)
407 s.close()
408 return 1
409
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000410 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000411 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000412 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000413 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000414 ok = self._remote("LOAD " + url)
415 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000416
Fred Drakec70b4482000-07-09 16:45:56 +0000417
Tim Peters658cba62001-02-09 20:06:00 +0000418#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000419# Platform support for Unix
420#
Fred Drakec70b4482000-07-09 16:45:56 +0000421
Georg Brandle8f24432005-10-03 14:16:44 +0000422# These are the right tests because all these Unix browsers require either
423# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000424
Neal Norwitz196f7332005-10-04 03:17:49 +0000425def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000426
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200427 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200428 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200429 register("xdg-open", None, BackgroundBrowser("xdg-open"))
430
431 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200432 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200433 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
434
Guido van Rossumd8faa362007-04-27 19:54:29 +0000435 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200436 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000437 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000438
Guido van Rossumd8faa362007-04-27 19:54:29 +0000439 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200440 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000441 register("kfmclient", Konqueror, Konqueror("kfmclient"))
442
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100443 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100444 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
445
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200446 # The Mozilla browsers
447 for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200448 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000449 register(browser, None, Mozilla(browser))
450
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200451 # The Netscape and old Mozilla browsers
452 for browser in ("mozilla-firefox",
453 "mozilla-firebird", "firebird",
454 "mozilla", "netscape"):
455 if shutil.which(browser):
456 register(browser, None, Netscape(browser))
457
Georg Brandle8f24432005-10-03 14:16:44 +0000458 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200459 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000460 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200461 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000462 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000463
Georg Brandle8f24432005-10-03 14:16:44 +0000464 # Gnome's Galeon and Epiphany
465 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200466 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000467 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000468
Georg Brandle8f24432005-10-03 14:16:44 +0000469 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200470 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000471 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000472
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800473 # Google Chrome/Chromium browsers
474 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200475 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800476 register(browser, None, Chrome(browser))
477
Georg Brandle8f24432005-10-03 14:16:44 +0000478 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200479 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000480 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000481
Georg Brandle8f24432005-10-03 14:16:44 +0000482 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200483 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000484 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000485
Georg Brandle8f24432005-10-03 14:16:44 +0000486 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200487 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000488 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000489
Neal Norwitz196f7332005-10-04 03:17:49 +0000490# Prefer X browsers if present
491if os.environ.get("DISPLAY"):
David Steelee3ce6952017-02-24 23:47:38 -0500492 try:
493 cmd = "xdg-settings get default-web-browser".split()
494 result = subprocess.check_output(cmd).decode().strip()
495 except (FileNotFoundError, subprocess.CalledProcessError):
496 pass
497 else:
498 _os_preferred_browser = result
499
Neal Norwitz196f7332005-10-04 03:17:49 +0000500 register_X_browsers()
501
Georg Brandle8f24432005-10-03 14:16:44 +0000502# Also try console browsers
503if os.environ.get("TERM"):
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100504 if shutil.which("www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100505 register("www-browser", None, GenericBrowser("www-browser"))
Georg Brandle8f24432005-10-03 14:16:44 +0000506 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200507 if shutil.which("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000508 register("links", None, GenericBrowser("links"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200509 if shutil.which("elinks"):
Georg Brandle8f24432005-10-03 14:16:44 +0000510 register("elinks", None, Elinks("elinks"))
511 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200512 if shutil.which("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000513 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000514 # The w3m browser <http://w3m.sourceforge.net/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200515 if shutil.which("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000516 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000517
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000518#
519# Platform support for Windows
520#
Fred Drakec70b4482000-07-09 16:45:56 +0000521
522if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000523 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000524 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000525 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700526 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200527 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000528 # [Error 22] No application is associated with the specified
529 # file for this operation: '<URL>'
530 return False
531 else:
532 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000533
534 _tryorder = []
535 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000536
Steve Dower2ebd8f52015-09-05 11:57:47 -0700537 # First try to use the default Windows browser
Guido van Rossumd8faa362007-04-27 19:54:29 +0000538 register("windows-default", WindowsDefault)
539
Steve Dower2ebd8f52015-09-05 11:57:47 -0700540 # Detect some common Windows browsers, fallback to IE
541 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
542 "Internet Explorer\\IEXPLORE.EXE")
543 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
544 "netscape", "opera", iexplore):
545 if shutil.which(browser):
546 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000547
Fred Drakec70b4482000-07-09 16:45:56 +0000548#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000549# Platform support for MacOS
550#
Fred Drakec70b4482000-07-09 16:45:56 +0000551
Georg Brandle8f24432005-10-03 14:16:44 +0000552if sys.platform == 'darwin':
553 # Adapted from patch submitted to SourceForge by Steven J. Burr
554 class MacOSX(BaseBrowser):
555 """Launcher class for Aqua browsers on Mac OS X
556
557 Optionally specify a browser name on instantiation. Note that this
558 will not work for Aqua browsers if the user has moved the application
559 package after installation.
560
561 If no browser is specified, the default browser, as specified in the
562 Internet System Preferences panel, will be used.
563 """
564 def __init__(self, name):
565 self.name = name
566
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000567 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000568 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000569 # hack for local urls
570 if not ':' in url:
571 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000572
Georg Brandle8f24432005-10-03 14:16:44 +0000573 # new must be 0 or 1
574 new = int(bool(new))
575 if self.name == "default":
576 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000577 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000578 else:
579 # User called get and chose a browser
580 if self.name == "OmniWeb":
581 toWindow = ""
582 else:
583 # Include toWindow parameter of OpenURL command for browsers
584 # that support it. 0 == new window; -1 == existing
585 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000586 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000587 script = '''tell application "%s"
588 activate
589 %s %s
590 end tell''' % (self.name, cmd, toWindow)
591 # Open pipe to AppleScript through osascript command
592 osapipe = os.popen("osascript", "w")
593 if osapipe is None:
594 return False
595 # Write script to osascript's stdin
596 osapipe.write(script)
597 rc = osapipe.close()
598 return not rc
599
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000600 class MacOSXOSAScript(BaseBrowser):
601 def __init__(self, name):
602 self._name = name
603
604 def open(self, url, new=0, autoraise=True):
605 if self._name == 'default':
606 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
607 else:
608 script = '''
609 tell application "%s"
610 activate
611 open location "%s"
612 end
613 '''%(self._name, url.replace('"', '%22'))
614
615 osapipe = os.popen("osascript", "w")
616 if osapipe is None:
617 return False
618
619 osapipe.write(script)
620 rc = osapipe.close()
621 return not rc
622
623
Georg Brandle8f24432005-10-03 14:16:44 +0000624 # Don't clear _tryorder or _browsers since OS X can use above Unix support
625 # (but we prefer using the OS X specific stuff)
David Steelee3ce6952017-02-24 23:47:38 -0500626 register("safari", None, MacOSXOSAScript('safari'), preferred=True)
627 register("firefox", None, MacOSXOSAScript('firefox'), preferred=True)
628 register("chrome", None, MacOSXOSAScript('chrome'), preferred=True)
629 register("MacOSX", None, MacOSXOSAScript('default'), preferred=True)
Georg Brandle8f24432005-10-03 14:16:44 +0000630
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000631
632# OK, now that we know what the default preference orders for each
633# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000634if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000635 _userchoices = os.environ["BROWSER"].split(os.pathsep)
636 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000637
Georg Brandle8f24432005-10-03 14:16:44 +0000638 # Treat choices in same way as if passed into get() but do register
639 # and prepend to _tryorder
640 for cmdline in _userchoices:
641 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000642 cmd = _synthesize(cmdline, -1)
643 if cmd[1] is None:
David Steelee3ce6952017-02-24 23:47:38 -0500644 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
Georg Brandle8f24432005-10-03 14:16:44 +0000645 cmdline = None # to make del work if _userchoices was empty
646 del cmdline
647 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000648
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000649# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000650
651
652def main():
653 import getopt
654 usage = """Usage: %s [-n | -t] url
655 -n: open new window
656 -t: open new tab""" % sys.argv[0]
657 try:
658 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000659 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000660 print(msg, file=sys.stderr)
661 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000662 sys.exit(1)
663 new_win = 0
664 for o, a in opts:
665 if o == '-n': new_win = 1
666 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000667 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000668 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000669 sys.exit(1)
670
671 url = args[0]
672 open(url, new_win)
673
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000674 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000675
Georg Brandle8f24432005-10-03 14:16:44 +0000676if __name__ == "__main__":
677 main()