blob: 5a356587c005800c1dde5d56b71eb90a43377fa6 [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
Tim Peters658cba62001-02-09 20:06:00 +000016_browsers = {} # Dictionary of available browser controllers
17_tryorder = [] # Preference order of available browsers
Fred Drakec70b4482000-07-09 16:45:56 +000018
Georg Brandle8f24432005-10-03 14:16:44 +000019def register(name, klass, instance=None, update_tryorder=1):
Fred Drakec70b4482000-07-09 16:45:56 +000020 """Register a browser connector and, optionally, connection."""
21 _browsers[name.lower()] = [klass, instance]
Georg Brandle8f24432005-10-03 14:16:44 +000022 if update_tryorder > 0:
23 _tryorder.append(name)
24 elif update_tryorder < 0:
25 _tryorder.insert(0, name)
Fred Drakec70b4482000-07-09 16:45:56 +000026
Eric S. Raymondf7f18512001-01-23 13:16:32 +000027def get(using=None):
28 """Return a browser launcher instance appropriate for the environment."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000029 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000030 alternatives = [using]
31 else:
32 alternatives = _tryorder
33 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000034 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000035 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000036 browser = shlex.split(browser)
37 if browser[-1] == '&':
38 return BackgroundBrowser(browser[:-1])
39 else:
40 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000041 else:
Georg Brandle8f24432005-10-03 14:16:44 +000042 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000043 try:
44 command = _browsers[browser.lower()]
45 except KeyError:
46 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000047 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000048 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000049 elif command[0] is not None:
50 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000051 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000052
53# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000054# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
55# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000056
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000057def open(url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +000058 for name in _tryorder:
59 browser = get(name)
60 if browser.open(url, new, autoraise):
61 return True
62 return False
Fred Drakec70b4482000-07-09 16:45:56 +000063
Fred Drake3f8f1642001-07-19 03:46:26 +000064def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000065 return open(url, 1)
66
67def open_new_tab(url):
68 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000069
Fred Drakef4e5bd92001-04-12 22:07:27 +000070
Georg Brandle8f24432005-10-03 14:16:44 +000071def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000072 """Attempt to synthesize a controller base on existing controllers.
73
74 This is useful to create a controller when a user specifies a path to
75 an entry in the BROWSER environment variable -- we can copy a general
76 controller to operate using a specific installation of the desired
77 browser in this way.
78
79 If we can't create a controller in this way, or if there is no
80 executable for the requested browser, return [None, None].
81
82 """
Georg Brandle8f24432005-10-03 14:16:44 +000083 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +020084 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +000085 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +000086 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +000087 try:
88 command = _browsers[name.lower()]
89 except KeyError:
90 return [None, None]
91 # now attempt to clone to fit the new name:
92 controller = command[1]
93 if controller and name.lower() == controller.basename:
94 import copy
95 controller = copy.copy(controller)
96 controller.name = browser
97 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000098 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +000099 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000100 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000101
Fred Drake3f8f1642001-07-19 03:46:26 +0000102
Georg Brandle8f24432005-10-03 14:16:44 +0000103# General parent classes
104
105class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000106 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000107
Georg Brandl23929f22006-01-20 21:03:35 +0000108 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000109
Georg Brandle8f24432005-10-03 14:16:44 +0000110 def __init__(self, name=""):
111 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000112 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000113
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000114 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000115 raise NotImplementedError
116
Georg Brandle8f24432005-10-03 14:16:44 +0000117 def open_new(self, url):
118 return self.open(url, 1)
119
120 def open_new_tab(self, url):
121 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000122
123
Georg Brandle8f24432005-10-03 14:16:44 +0000124class GenericBrowser(BaseBrowser):
125 """Class for all browsers started with a command
126 and without remote functionality."""
127
Georg Brandl23929f22006-01-20 21:03:35 +0000128 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000129 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000130 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000131 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000132 else:
133 # name should be a list with arguments
134 self.name = name[0]
135 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000136 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000137
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000138 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000139 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000140 for arg in self.args]
141 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000142 if sys.platform[:3] == 'win':
143 p = subprocess.Popen(cmdline)
144 else:
145 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000146 return not p.wait()
147 except OSError:
148 return False
149
150
151class BackgroundBrowser(GenericBrowser):
152 """Class for all browsers which are to be started in the
153 background."""
154
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000155 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000156 cmdline = [self.name] + [arg.replace("%s", url)
157 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000158 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000159 if sys.platform[:3] == 'win':
160 p = subprocess.Popen(cmdline)
161 else:
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700162 p = subprocess.Popen(cmdline, close_fds=True,
163 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000164 return (p.poll() is None)
165 except OSError:
166 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000167
168
Georg Brandle8f24432005-10-03 14:16:44 +0000169class UnixBrowser(BaseBrowser):
170 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000171
Georg Brandle8f24432005-10-03 14:16:44 +0000172 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400173 background = False
174 redirect_stdout = True
175 # In remote_args, %s will be replaced with the requested URL. %action will
176 # be replaced depending on the value of 'new' passed to open.
177 # remote_action is used for new=0 (open). If newwin is not None, it is
178 # used for new=1 (open_new). If newtab is not None, it is used for
179 # new=3 (open_new_tab). After both substitutions are made, any empty
180 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000181 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000182 remote_action = None
183 remote_action_newwin = None
184 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000185
Georg Brandl23929f22006-01-20 21:03:35 +0000186 def _invoke(self, args, remote, autoraise):
187 raise_opt = []
188 if remote and self.raise_opts:
189 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000190 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000191 opt = self.raise_opts[autoraise]
192 if opt: raise_opt = [opt]
193
194 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000195
Georg Brandl23929f22006-01-20 21:03:35 +0000196 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400197 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000198 else:
199 # for TTY browsers, we need stdin/out
200 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000201 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
202 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400203 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000204 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200205 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000206 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200207 try:
208 rc = p.wait(5)
209 # if remote call failed, open() will try direct invocation
210 return not rc
211 except subprocess.TimeoutExpired:
212 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000213 elif self.background:
214 if p.poll() is None:
215 return True
216 else:
217 return False
218 else:
219 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000220
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000221 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000222 if new == 0:
223 action = self.remote_action
224 elif new == 1:
225 action = self.remote_action_newwin
226 elif new == 2:
227 if self.remote_action_newtab is None:
228 action = self.remote_action_newwin
229 else:
230 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000231 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000232 raise Error("Bad 'new' parameter to open(); " +
233 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000234
Georg Brandl23929f22006-01-20 21:03:35 +0000235 args = [arg.replace("%s", url).replace("%action", action)
236 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400237 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000238 success = self._invoke(args, True, autoraise)
239 if not success:
240 # remote invocation failed, try straight way
241 args = [arg.replace("%s", url) for arg in self.args]
242 return self._invoke(args, False, False)
243 else:
244 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000245
246
Georg Brandle8f24432005-10-03 14:16:44 +0000247class Mozilla(UnixBrowser):
248 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000249
Georg Brandl23929f22006-01-20 21:03:35 +0000250 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000251 remote_args = ['-remote', 'openURL(%s%action)']
252 remote_action = ""
253 remote_action_newwin = ",new-window"
254 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000255 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000256
Georg Brandle8f24432005-10-03 14:16:44 +0000257Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000258
259
Georg Brandle8f24432005-10-03 14:16:44 +0000260class Galeon(UnixBrowser):
261 """Launcher class for Galeon/Epiphany browsers."""
262
Georg Brandl23929f22006-01-20 21:03:35 +0000263 raise_opts = ["-noraise", ""]
264 remote_args = ['%action', '%s']
265 remote_action = "-n"
266 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000267 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000268
269
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800270class Chrome(UnixBrowser):
271 "Launcher class for Google Chrome browser."
272
273 remote_args = ['%action', '%s']
274 remote_action = ""
275 remote_action_newwin = "--new-window"
276 remote_action_newtab = ""
277 background = True
278
279Chromium = Chrome
280
281
Georg Brandle8f24432005-10-03 14:16:44 +0000282class Opera(UnixBrowser):
283 "Launcher class for Opera browser."
284
Terry Reedydad532f2010-12-28 19:30:19 +0000285 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000286 remote_args = ['-remote', 'openURL(%s%action)']
287 remote_action = ""
288 remote_action_newwin = ",new-window"
289 remote_action_newtab = ",new-page"
290 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000291
292
293class Elinks(UnixBrowser):
294 "Launcher class for Elinks browsers."
295
Georg Brandl23929f22006-01-20 21:03:35 +0000296 remote_args = ['-remote', 'openURL(%s%action)']
297 remote_action = ""
298 remote_action_newwin = ",new-window"
299 remote_action_newtab = ",new-tab"
300 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000301
Georg Brandl23929f22006-01-20 21:03:35 +0000302 # elinks doesn't like its stdout to be redirected -
303 # it uses redirected stdout as a signal to do -dump
304 redirect_stdout = False
305
306
307class Konqueror(BaseBrowser):
308 """Controller for the KDE File Manager (kfm, or Konqueror).
309
310 See the output of ``kfmclient --commands``
311 for more information on the Konqueror remote-control interface.
312 """
313
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000314 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000315 # XXX Currently I know no way to prevent KFM from opening a new win.
316 if new == 2:
317 action = "newTab"
318 else:
319 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000320
R David Murray02ca1442012-09-03 12:44:29 -0400321 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000322
Georg Brandl23929f22006-01-20 21:03:35 +0000323 try:
324 p = subprocess.Popen(["kfmclient", action, url],
325 close_fds=True, stdin=devnull,
326 stdout=devnull, stderr=devnull)
327 except OSError:
328 # fall through to next variant
329 pass
330 else:
331 p.wait()
332 # kfmclient's return code unfortunately has no meaning as it seems
333 return True
334
335 try:
336 p = subprocess.Popen(["konqueror", "--silent", url],
337 close_fds=True, stdin=devnull,
338 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700339 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000340 except OSError:
341 # fall through to next variant
342 pass
343 else:
344 if p.poll() is None:
345 # Should be running now.
346 return True
Tim Peters887c0802006-01-20 23:40:56 +0000347
Georg Brandl23929f22006-01-20 21:03:35 +0000348 try:
349 p = subprocess.Popen(["kfm", "-d", 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 return False
355 else:
356 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000357
358
359class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000360 # There should be a way to maintain a connection to Grail, but the
361 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000362 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000363 def _find_grail_rc(self):
364 import glob
365 import pwd
366 import socket
367 import tempfile
368 tempdir = os.path.join(tempfile.gettempdir(),
369 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000370 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000371 filename = os.path.join(tempdir, user + "-*")
372 maybes = glob.glob(filename)
373 if not maybes:
374 return None
375 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
376 for fn in maybes:
377 # need to PING each one until we find one that's live
378 try:
379 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200380 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000381 # no good; attempt to clean it out, but don't fail:
382 try:
383 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200384 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000385 pass
386 else:
387 return s
388
389 def _remote(self, action):
390 s = self._find_grail_rc()
391 if not s:
392 return 0
393 s.send(action)
394 s.close()
395 return 1
396
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000397 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000398 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000399 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000400 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000401 ok = self._remote("LOAD " + url)
402 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000403
Fred Drakec70b4482000-07-09 16:45:56 +0000404
Tim Peters658cba62001-02-09 20:06:00 +0000405#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000406# Platform support for Unix
407#
Fred Drakec70b4482000-07-09 16:45:56 +0000408
Georg Brandle8f24432005-10-03 14:16:44 +0000409# These are the right tests because all these Unix browsers require either
410# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000411
Neal Norwitz196f7332005-10-04 03:17:49 +0000412def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000413
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200414 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200415 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200416 register("xdg-open", None, BackgroundBrowser("xdg-open"))
417
418 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200419 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200420 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
421
Guido van Rossumd8faa362007-04-27 19:54:29 +0000422 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200423 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000424 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000425
Guido van Rossumd8faa362007-04-27 19:54:29 +0000426 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200427 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000428 register("kfmclient", Konqueror, Konqueror("kfmclient"))
429
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100430 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100431 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
432
Guido van Rossumd8faa362007-04-27 19:54:29 +0000433 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000434 for browser in ("mozilla-firefox", "firefox",
435 "mozilla-firebird", "firebird",
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100436 "iceweasel", "iceape",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000437 "seamonkey", "mozilla", "netscape"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200438 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000439 register(browser, None, Mozilla(browser))
440
Georg Brandle8f24432005-10-03 14:16:44 +0000441 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200442 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000443 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200444 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000445 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000446
Georg Brandle8f24432005-10-03 14:16:44 +0000447 # Gnome's Galeon and Epiphany
448 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200449 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000450 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000451
Georg Brandle8f24432005-10-03 14:16:44 +0000452 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200453 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000454 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000455
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800456 # Google Chrome/Chromium browsers
457 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200458 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800459 register(browser, None, Chrome(browser))
460
Georg Brandle8f24432005-10-03 14:16:44 +0000461 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200462 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000463 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000464
Georg Brandle8f24432005-10-03 14:16:44 +0000465 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200466 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000467 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000468
Georg Brandle8f24432005-10-03 14:16:44 +0000469 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200470 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000471 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000472
Neal Norwitz196f7332005-10-04 03:17:49 +0000473# Prefer X browsers if present
474if os.environ.get("DISPLAY"):
475 register_X_browsers()
476
Georg Brandle8f24432005-10-03 14:16:44 +0000477# Also try console browsers
478if os.environ.get("TERM"):
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100479 if shutil.which("www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100480 register("www-browser", None, GenericBrowser("www-browser"))
Georg Brandle8f24432005-10-03 14:16:44 +0000481 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200482 if shutil.which("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000483 register("links", None, GenericBrowser("links"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200484 if shutil.which("elinks"):
Georg Brandle8f24432005-10-03 14:16:44 +0000485 register("elinks", None, Elinks("elinks"))
486 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200487 if shutil.which("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000488 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000489 # The w3m browser <http://w3m.sourceforge.net/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200490 if shutil.which("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000491 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000492
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000493#
494# Platform support for Windows
495#
Fred Drakec70b4482000-07-09 16:45:56 +0000496
497if sys.platform[:3] == "win":
Steve Dower93cfeb92015-06-07 21:35:39 -0700498
Georg Brandle8f24432005-10-03 14:16:44 +0000499 class WindowsDefault(BaseBrowser):
Steve Dower93cfeb92015-06-07 21:35:39 -0700500 # Windows Default opening arguments.
501
502 cmd = "start"
503 newwindow = ""
504 newtab = ""
505
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000506 def open(self, url, new=0, autoraise=True):
Steve Dower93cfeb92015-06-07 21:35:39 -0700507 # Format the command for optional arguments and add the url.
508 if new == 1:
509 self.cmd += " " + self.newwindow
510 elif new == 2:
511 self.cmd += " " + self.newtab
512 self.cmd += " " + url
Guido van Rossumd8faa362007-04-27 19:54:29 +0000513 try:
Steve Dower93cfeb92015-06-07 21:35:39 -0700514 subprocess.call(self.cmd, shell=True)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200515 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000516 # [Error 22] No application is associated with the specified
517 # file for this operation: '<URL>'
518 return False
519 else:
520 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000521
Steve Dower93cfeb92015-06-07 21:35:39 -0700522
523 # Windows Sub-Classes for commonly used browsers.
524
525 class InternetExplorer(WindowsDefault):
526 """Launcher class for Internet Explorer browser"""
527
528 cmd = "start iexplore.exe"
529 newwindow = ""
530 newtab = ""
531
532
533 class WinChrome(WindowsDefault):
534 """Launcher class for windows specific Google Chrome browser"""
535
536 cmd = "start chrome.exe"
537 newwindow = "-new-window"
538 newtab = "-new-tab"
539
540
541 class WinFireFox(WindowsDefault):
542 """Launcher class for windows specific Firefox browser"""
543
544 cmd = "start firefox.exe"
545 newwindow = "-new-window"
546 newtab = "-new-tab"
547
548
549 class WinOpera(WindowsDefault):
550 """Launcher class for windows specific Opera browser"""
551
552 cmd = "start opera"
553 newwindow = ""
554 newtab = ""
555
556
557 class WinSeaMonkey(WindowsDefault):
558 """Launcher class for windows specific SeaMonkey browser"""
559
560 cmd = "start seamonkey"
561 newwinow = ""
562 newtab = ""
563
564
Georg Brandle8f24432005-10-03 14:16:44 +0000565 _tryorder = []
566 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000567
Steve Dower93cfeb92015-06-07 21:35:39 -0700568 # First try to use the default Windows browser.
Guido van Rossumd8faa362007-04-27 19:54:29 +0000569 register("windows-default", WindowsDefault)
570
Steve Dower93cfeb92015-06-07 21:35:39 -0700571 def find_windows_browsers():
572 """ Access the windows registry to determine
573 what browsers are on the system.
574 """
575
576 import winreg
577 HKLM = winreg.HKEY_LOCAL_MACHINE
578 subkey = r'Software\Clients\StartMenuInternet'
579 read32 = winreg.KEY_READ | winreg.KEY_WOW64_32KEY
580 read64 = winreg.KEY_READ | winreg.KEY_WOW64_64KEY
581 key32 = winreg.OpenKey(HKLM, subkey, access=read32)
582 key64 = winreg.OpenKey(HKLM, subkey, access=read64)
583
584 # Return a list of browsers found in the registry
585 # Check if there are any different browsers in the
586 # 32 bit location instead of the 64 bit location.
587 browsers = []
588 i = 0
589 while True:
590 try:
591 browsers.append(winreg.EnumKey(key32, i))
592 except EnvironmentError:
593 break
594 i += 1
595
596 i = 0
597 while True:
598 try:
599 browsers.append(winreg.EnumKey(key64, i))
600 except EnvironmentError:
601 break
602 i += 1
603
604 winreg.CloseKey(key32)
605 winreg.CloseKey(key64)
606
607 return browsers
608
609 # Detect some common windows browsers
610 for browser in find_windows_browsers():
611 browser = browser.lower()
612 if "iexplore" in browser:
613 register("iexplore", None, InternetExplorer("iexplore"))
614 elif "chrome" in browser:
615 register("chrome", None, WinChrome("chrome"))
616 elif "firefox" in browser:
617 register("firefox", None, WinFireFox("firefox"))
618 elif "opera" in browser:
619 register("opera", None, WinOpera("opera"))
620 elif "seamonkey" in browser:
621 register("seamonkey", None, WinSeaMonkey("seamonkey"))
622 else:
623 register(browser, None, WindowsDefault(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000624
Fred Drakec70b4482000-07-09 16:45:56 +0000625#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000626# Platform support for MacOS
627#
Fred Drakec70b4482000-07-09 16:45:56 +0000628
Georg Brandle8f24432005-10-03 14:16:44 +0000629if sys.platform == 'darwin':
630 # Adapted from patch submitted to SourceForge by Steven J. Burr
631 class MacOSX(BaseBrowser):
632 """Launcher class for Aqua browsers on Mac OS X
633
634 Optionally specify a browser name on instantiation. Note that this
635 will not work for Aqua browsers if the user has moved the application
636 package after installation.
637
638 If no browser is specified, the default browser, as specified in the
639 Internet System Preferences panel, will be used.
640 """
641 def __init__(self, name):
642 self.name = name
643
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000644 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000645 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000646 # hack for local urls
647 if not ':' in url:
648 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000649
Georg Brandle8f24432005-10-03 14:16:44 +0000650 # new must be 0 or 1
651 new = int(bool(new))
652 if self.name == "default":
653 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000654 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000655 else:
656 # User called get and chose a browser
657 if self.name == "OmniWeb":
658 toWindow = ""
659 else:
660 # Include toWindow parameter of OpenURL command for browsers
661 # that support it. 0 == new window; -1 == existing
662 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000663 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000664 script = '''tell application "%s"
665 activate
666 %s %s
667 end tell''' % (self.name, cmd, toWindow)
668 # Open pipe to AppleScript through osascript command
669 osapipe = os.popen("osascript", "w")
670 if osapipe is None:
671 return False
672 # Write script to osascript's stdin
673 osapipe.write(script)
674 rc = osapipe.close()
675 return not rc
676
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000677 class MacOSXOSAScript(BaseBrowser):
678 def __init__(self, name):
679 self._name = name
680
681 def open(self, url, new=0, autoraise=True):
682 if self._name == 'default':
683 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
684 else:
685 script = '''
686 tell application "%s"
687 activate
688 open location "%s"
689 end
690 '''%(self._name, url.replace('"', '%22'))
691
692 osapipe = os.popen("osascript", "w")
693 if osapipe is None:
694 return False
695
696 osapipe.write(script)
697 rc = osapipe.close()
698 return not rc
699
700
Georg Brandle8f24432005-10-03 14:16:44 +0000701 # Don't clear _tryorder or _browsers since OS X can use above Unix support
702 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000703 register("safari", None, MacOSXOSAScript('safari'), -1)
704 register("firefox", None, MacOSXOSAScript('firefox'), -1)
705 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000706
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000707
708# OK, now that we know what the default preference orders for each
709# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000710if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000711 _userchoices = os.environ["BROWSER"].split(os.pathsep)
712 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000713
Georg Brandle8f24432005-10-03 14:16:44 +0000714 # Treat choices in same way as if passed into get() but do register
715 # and prepend to _tryorder
716 for cmdline in _userchoices:
717 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000718 cmd = _synthesize(cmdline, -1)
719 if cmd[1] is None:
720 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000721 cmdline = None # to make del work if _userchoices was empty
722 del cmdline
723 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000724
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000725# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000726
727
728def main():
729 import getopt
730 usage = """Usage: %s [-n | -t] url
731 -n: open new window
732 -t: open new tab""" % sys.argv[0]
733 try:
734 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000735 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000736 print(msg, file=sys.stderr)
737 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000738 sys.exit(1)
739 new_win = 0
740 for o, a in opts:
741 if o == '-n': new_win = 1
742 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000743 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000744 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000745 sys.exit(1)
746
747 url = args[0]
748 open(url, new_win)
749
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000750 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000751
Georg Brandle8f24432005-10-03 14:16:44 +0000752if __name__ == "__main__":
753 main()