blob: 9e47084a91b587be0162b54aaeafbd85b1ffd6f0 [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:
162 setsid = getattr(os, 'setsid', None)
163 if not setsid:
164 setsid = getattr(os, 'setpgrp', None)
165 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
Georg Brandl23929f22006-01-20 21:03:35 +0000166 return (p.poll() is None)
167 except OSError:
168 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000169
170
Georg Brandle8f24432005-10-03 14:16:44 +0000171class UnixBrowser(BaseBrowser):
172 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000173
Georg Brandle8f24432005-10-03 14:16:44 +0000174 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400175 background = False
176 redirect_stdout = True
177 # In remote_args, %s will be replaced with the requested URL. %action will
178 # be replaced depending on the value of 'new' passed to open.
179 # remote_action is used for new=0 (open). If newwin is not None, it is
180 # used for new=1 (open_new). If newtab is not None, it is used for
181 # new=3 (open_new_tab). After both substitutions are made, any empty
182 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000183 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000184 remote_action = None
185 remote_action_newwin = None
186 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000187
Georg Brandl23929f22006-01-20 21:03:35 +0000188 def _invoke(self, args, remote, autoraise):
189 raise_opt = []
190 if remote and self.raise_opts:
191 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000192 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000193 opt = self.raise_opts[autoraise]
194 if opt: raise_opt = [opt]
195
196 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000197
Georg Brandl23929f22006-01-20 21:03:35 +0000198 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400199 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000200 else:
201 # for TTY browsers, we need stdin/out
202 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000203 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
204 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400205 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000206 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200207 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000208 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200209 try:
210 rc = p.wait(5)
211 # if remote call failed, open() will try direct invocation
212 return not rc
213 except subprocess.TimeoutExpired:
214 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000215 elif self.background:
216 if p.poll() is None:
217 return True
218 else:
219 return False
220 else:
221 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000222
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000223 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000224 if new == 0:
225 action = self.remote_action
226 elif new == 1:
227 action = self.remote_action_newwin
228 elif new == 2:
229 if self.remote_action_newtab is None:
230 action = self.remote_action_newwin
231 else:
232 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000233 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000234 raise Error("Bad 'new' parameter to open(); " +
235 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000236
Georg Brandl23929f22006-01-20 21:03:35 +0000237 args = [arg.replace("%s", url).replace("%action", action)
238 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400239 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000240 success = self._invoke(args, True, autoraise)
241 if not success:
242 # remote invocation failed, try straight way
243 args = [arg.replace("%s", url) for arg in self.args]
244 return self._invoke(args, False, False)
245 else:
246 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000247
248
Georg Brandle8f24432005-10-03 14:16:44 +0000249class Mozilla(UnixBrowser):
250 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000251
Georg Brandl23929f22006-01-20 21:03:35 +0000252 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000253 remote_args = ['-remote', 'openURL(%s%action)']
254 remote_action = ""
255 remote_action_newwin = ",new-window"
256 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000257 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000258
Georg Brandle8f24432005-10-03 14:16:44 +0000259Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000260
261
Georg Brandle8f24432005-10-03 14:16:44 +0000262class Galeon(UnixBrowser):
263 """Launcher class for Galeon/Epiphany browsers."""
264
Georg Brandl23929f22006-01-20 21:03:35 +0000265 raise_opts = ["-noraise", ""]
266 remote_args = ['%action', '%s']
267 remote_action = "-n"
268 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000269 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000270
271
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800272class Chrome(UnixBrowser):
273 "Launcher class for Google Chrome browser."
274
275 remote_args = ['%action', '%s']
276 remote_action = ""
277 remote_action_newwin = "--new-window"
278 remote_action_newtab = ""
279 background = True
280
281Chromium = Chrome
282
283
Georg Brandle8f24432005-10-03 14:16:44 +0000284class Opera(UnixBrowser):
285 "Launcher class for Opera browser."
286
Terry Reedydad532f2010-12-28 19:30:19 +0000287 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000288 remote_args = ['-remote', 'openURL(%s%action)']
289 remote_action = ""
290 remote_action_newwin = ",new-window"
291 remote_action_newtab = ",new-page"
292 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000293
294
295class Elinks(UnixBrowser):
296 "Launcher class for Elinks browsers."
297
Georg Brandl23929f22006-01-20 21:03:35 +0000298 remote_args = ['-remote', 'openURL(%s%action)']
299 remote_action = ""
300 remote_action_newwin = ",new-window"
301 remote_action_newtab = ",new-tab"
302 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000303
Georg Brandl23929f22006-01-20 21:03:35 +0000304 # elinks doesn't like its stdout to be redirected -
305 # it uses redirected stdout as a signal to do -dump
306 redirect_stdout = False
307
308
309class Konqueror(BaseBrowser):
310 """Controller for the KDE File Manager (kfm, or Konqueror).
311
312 See the output of ``kfmclient --commands``
313 for more information on the Konqueror remote-control interface.
314 """
315
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000316 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000317 # XXX Currently I know no way to prevent KFM from opening a new win.
318 if new == 2:
319 action = "newTab"
320 else:
321 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000322
R David Murray02ca1442012-09-03 12:44:29 -0400323 devnull = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000324 # if possible, put browser in separate process group, so
325 # keyboard interrupts don't affect browser as well as Python
326 setsid = getattr(os, 'setsid', None)
327 if not setsid:
328 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000329
Georg Brandl23929f22006-01-20 21:03:35 +0000330 try:
331 p = subprocess.Popen(["kfmclient", action, url],
332 close_fds=True, stdin=devnull,
333 stdout=devnull, stderr=devnull)
334 except OSError:
335 # fall through to next variant
336 pass
337 else:
338 p.wait()
339 # kfmclient's return code unfortunately has no meaning as it seems
340 return True
341
342 try:
343 p = subprocess.Popen(["konqueror", "--silent", url],
344 close_fds=True, stdin=devnull,
345 stdout=devnull, stderr=devnull,
346 preexec_fn=setsid)
347 except OSError:
348 # fall through to next variant
349 pass
350 else:
351 if p.poll() is None:
352 # Should be running now.
353 return True
Tim Peters887c0802006-01-20 23:40:56 +0000354
Georg Brandl23929f22006-01-20 21:03:35 +0000355 try:
356 p = subprocess.Popen(["kfm", "-d", url],
357 close_fds=True, stdin=devnull,
358 stdout=devnull, stderr=devnull,
359 preexec_fn=setsid)
360 except OSError:
361 return False
362 else:
363 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000364
365
366class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000367 # There should be a way to maintain a connection to Grail, but the
368 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000369 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000370 def _find_grail_rc(self):
371 import glob
372 import pwd
373 import socket
374 import tempfile
375 tempdir = os.path.join(tempfile.gettempdir(),
376 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000377 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000378 filename = os.path.join(tempdir, user + "-*")
379 maybes = glob.glob(filename)
380 if not maybes:
381 return None
382 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
383 for fn in maybes:
384 # need to PING each one until we find one that's live
385 try:
386 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200387 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000388 # no good; attempt to clean it out, but don't fail:
389 try:
390 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200391 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000392 pass
393 else:
394 return s
395
396 def _remote(self, action):
397 s = self._find_grail_rc()
398 if not s:
399 return 0
400 s.send(action)
401 s.close()
402 return 1
403
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000404 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000405 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000406 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000407 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000408 ok = self._remote("LOAD " + url)
409 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000410
Fred Drakec70b4482000-07-09 16:45:56 +0000411
Tim Peters658cba62001-02-09 20:06:00 +0000412#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000413# Platform support for Unix
414#
Fred Drakec70b4482000-07-09 16:45:56 +0000415
Georg Brandle8f24432005-10-03 14:16:44 +0000416# These are the right tests because all these Unix browsers require either
417# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000418
Neal Norwitz196f7332005-10-04 03:17:49 +0000419def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000420
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200421 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200422 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200423 register("xdg-open", None, BackgroundBrowser("xdg-open"))
424
425 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200426 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200427 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
428
Guido van Rossumd8faa362007-04-27 19:54:29 +0000429 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200430 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000431 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000432
Guido van Rossumd8faa362007-04-27 19:54:29 +0000433 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200434 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000435 register("kfmclient", Konqueror, Konqueror("kfmclient"))
436
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100437 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100438 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
439
Guido van Rossumd8faa362007-04-27 19:54:29 +0000440 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000441 for browser in ("mozilla-firefox", "firefox",
442 "mozilla-firebird", "firebird",
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100443 "iceweasel", "iceape",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000444 "seamonkey", "mozilla", "netscape"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200445 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000446 register(browser, None, Mozilla(browser))
447
Georg Brandle8f24432005-10-03 14:16:44 +0000448 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200449 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000450 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200451 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000452 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000453
Georg Brandle8f24432005-10-03 14:16:44 +0000454 # Gnome's Galeon and Epiphany
455 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200456 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000457 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000458
Georg Brandle8f24432005-10-03 14:16:44 +0000459 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200460 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000461 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000462
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800463 # Google Chrome/Chromium browsers
464 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200465 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800466 register(browser, None, Chrome(browser))
467
Georg Brandle8f24432005-10-03 14:16:44 +0000468 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200469 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000470 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000471
Georg Brandle8f24432005-10-03 14:16:44 +0000472 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200473 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000474 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000475
Georg Brandle8f24432005-10-03 14:16:44 +0000476 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200477 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000478 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000479
Neal Norwitz196f7332005-10-04 03:17:49 +0000480# Prefer X browsers if present
481if os.environ.get("DISPLAY"):
482 register_X_browsers()
483
Georg Brandle8f24432005-10-03 14:16:44 +0000484# Also try console browsers
485if os.environ.get("TERM"):
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100486 if shutil.which("www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100487 register("www-browser", None, GenericBrowser("www-browser"))
Georg Brandle8f24432005-10-03 14:16:44 +0000488 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200489 if shutil.which("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000490 register("links", None, GenericBrowser("links"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200491 if shutil.which("elinks"):
Georg Brandle8f24432005-10-03 14:16:44 +0000492 register("elinks", None, Elinks("elinks"))
493 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200494 if shutil.which("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000495 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000496 # The w3m browser <http://w3m.sourceforge.net/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200497 if shutil.which("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000498 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000499
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000500#
501# Platform support for Windows
502#
Fred Drakec70b4482000-07-09 16:45:56 +0000503
504if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000505 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000506 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000507 try:
508 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200509 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000510 # [Error 22] No application is associated with the specified
511 # file for this operation: '<URL>'
512 return False
513 else:
514 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000515
516 _tryorder = []
517 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000518
519 # First try to use the default Windows browser
520 register("windows-default", WindowsDefault)
521
522 # Detect some common Windows browsers, fallback to IE
523 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
524 "Internet Explorer\\IEXPLORE.EXE")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000525 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
Guido van Rossumd8faa362007-04-27 19:54:29 +0000526 "netscape", "opera", iexplore):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200527 if shutil.which(browser):
Georg Brandl23929f22006-01-20 21:03:35 +0000528 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000529
Fred Drakec70b4482000-07-09 16:45:56 +0000530#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000531# Platform support for MacOS
532#
Fred Drakec70b4482000-07-09 16:45:56 +0000533
Georg Brandle8f24432005-10-03 14:16:44 +0000534if sys.platform == 'darwin':
535 # Adapted from patch submitted to SourceForge by Steven J. Burr
536 class MacOSX(BaseBrowser):
537 """Launcher class for Aqua browsers on Mac OS X
538
539 Optionally specify a browser name on instantiation. Note that this
540 will not work for Aqua browsers if the user has moved the application
541 package after installation.
542
543 If no browser is specified, the default browser, as specified in the
544 Internet System Preferences panel, will be used.
545 """
546 def __init__(self, name):
547 self.name = name
548
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000549 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000550 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000551 # hack for local urls
552 if not ':' in url:
553 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000554
Georg Brandle8f24432005-10-03 14:16:44 +0000555 # new must be 0 or 1
556 new = int(bool(new))
557 if self.name == "default":
558 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000559 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000560 else:
561 # User called get and chose a browser
562 if self.name == "OmniWeb":
563 toWindow = ""
564 else:
565 # Include toWindow parameter of OpenURL command for browsers
566 # that support it. 0 == new window; -1 == existing
567 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000568 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000569 script = '''tell application "%s"
570 activate
571 %s %s
572 end tell''' % (self.name, cmd, toWindow)
573 # Open pipe to AppleScript through osascript command
574 osapipe = os.popen("osascript", "w")
575 if osapipe is None:
576 return False
577 # Write script to osascript's stdin
578 osapipe.write(script)
579 rc = osapipe.close()
580 return not rc
581
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000582 class MacOSXOSAScript(BaseBrowser):
583 def __init__(self, name):
584 self._name = name
585
586 def open(self, url, new=0, autoraise=True):
587 if self._name == 'default':
588 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
589 else:
590 script = '''
591 tell application "%s"
592 activate
593 open location "%s"
594 end
595 '''%(self._name, url.replace('"', '%22'))
596
597 osapipe = os.popen("osascript", "w")
598 if osapipe is None:
599 return False
600
601 osapipe.write(script)
602 rc = osapipe.close()
603 return not rc
604
605
Georg Brandle8f24432005-10-03 14:16:44 +0000606 # Don't clear _tryorder or _browsers since OS X can use above Unix support
607 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000608 register("safari", None, MacOSXOSAScript('safari'), -1)
609 register("firefox", None, MacOSXOSAScript('firefox'), -1)
610 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000611
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000612
613# OK, now that we know what the default preference orders for each
614# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000615if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000616 _userchoices = os.environ["BROWSER"].split(os.pathsep)
617 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000618
Georg Brandle8f24432005-10-03 14:16:44 +0000619 # Treat choices in same way as if passed into get() but do register
620 # and prepend to _tryorder
621 for cmdline in _userchoices:
622 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000623 cmd = _synthesize(cmdline, -1)
624 if cmd[1] is None:
625 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000626 cmdline = None # to make del work if _userchoices was empty
627 del cmdline
628 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000629
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000630# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000631
632
633def main():
634 import getopt
635 usage = """Usage: %s [-n | -t] url
636 -n: open new window
637 -t: open new tab""" % sys.argv[0]
638 try:
639 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000640 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000641 print(msg, file=sys.stderr)
642 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000643 sys.exit(1)
644 new_win = 0
645 for o, a in opts:
646 if o == '-n': new_win = 1
647 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000648 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000649 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000650 sys.exit(1)
651
652 url = args[0]
653 open(url, new_win)
654
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000655 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000656
Georg Brandle8f24432005-10-03 14:16:44 +0000657if __name__ == "__main__":
658 main()