blob: f1a071b779f9e6fcba04fcb7a1408b18bd08ef31 [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
Fred Drakeee763952008-12-10 06:02:39 +00005import io
Fred Drakec70b4482000-07-09 16:45:56 +00006import os
Guido van Rossumd8faa362007-04-27 19:54:29 +00007import shlex
Fred Drakec70b4482000-07-09 16:45:56 +00008import sys
Georg Brandle8f24432005-10-03 14:16:44 +00009import stat
Georg Brandl23929f22006-01-20 21:03:35 +000010import subprocess
11import time
Fred Drakec70b4482000-07-09 16:45:56 +000012
Georg Brandle8f24432005-10-03 14:16:44 +000013__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
Skip Montanaro40fc1602001-03-01 04:27:19 +000014
Fred Drakec70b4482000-07-09 16:45:56 +000015class Error(Exception):
16 pass
17
Tim Peters658cba62001-02-09 20:06:00 +000018_browsers = {} # Dictionary of available browser controllers
19_tryorder = [] # Preference order of available browsers
Fred Drakec70b4482000-07-09 16:45:56 +000020
Georg Brandle8f24432005-10-03 14:16:44 +000021def register(name, klass, instance=None, update_tryorder=1):
Fred Drakec70b4482000-07-09 16:45:56 +000022 """Register a browser connector and, optionally, connection."""
23 _browsers[name.lower()] = [klass, instance]
Georg Brandle8f24432005-10-03 14:16:44 +000024 if update_tryorder > 0:
25 _tryorder.append(name)
26 elif update_tryorder < 0:
27 _tryorder.insert(0, name)
Fred Drakec70b4482000-07-09 16:45:56 +000028
Eric S. Raymondf7f18512001-01-23 13:16:32 +000029def get(using=None):
30 """Return a browser launcher instance appropriate for the environment."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000031 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000032 alternatives = [using]
33 else:
34 alternatives = _tryorder
35 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000036 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000037 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000038 browser = shlex.split(browser)
39 if browser[-1] == '&':
40 return BackgroundBrowser(browser[:-1])
41 else:
42 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000043 else:
Georg Brandle8f24432005-10-03 14:16:44 +000044 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000045 try:
46 command = _browsers[browser.lower()]
47 except KeyError:
48 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000049 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000050 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000051 elif command[0] is not None:
52 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000053 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000054
55# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000056# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
57# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000058
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000059def open(url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +000060 for name in _tryorder:
61 browser = get(name)
62 if browser.open(url, new, autoraise):
63 return True
64 return False
Fred Drakec70b4482000-07-09 16:45:56 +000065
Fred Drake3f8f1642001-07-19 03:46:26 +000066def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000067 return open(url, 1)
68
69def open_new_tab(url):
70 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000071
Fred Drakef4e5bd92001-04-12 22:07:27 +000072
Georg Brandle8f24432005-10-03 14:16:44 +000073def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000074 """Attempt to synthesize a controller base on existing controllers.
75
76 This is useful to create a controller when a user specifies a path to
77 an entry in the BROWSER environment variable -- we can copy a general
78 controller to operate using a specific installation of the desired
79 browser in this way.
80
81 If we can't create a controller in this way, or if there is no
82 executable for the requested browser, return [None, None].
83
84 """
Georg Brandle8f24432005-10-03 14:16:44 +000085 cmd = browser.split()[0]
86 if not _iscommand(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +000087 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +000088 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +000089 try:
90 command = _browsers[name.lower()]
91 except KeyError:
92 return [None, None]
93 # now attempt to clone to fit the new name:
94 controller = command[1]
95 if controller and name.lower() == controller.basename:
96 import copy
97 controller = copy.copy(controller)
98 controller.name = browser
99 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +0000100 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000101 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000102 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000103
Fred Drake3f8f1642001-07-19 03:46:26 +0000104
Georg Brandle8f24432005-10-03 14:16:44 +0000105if sys.platform[:3] == "win":
106 def _isexecutable(cmd):
107 cmd = cmd.lower()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000108 if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):
Georg Brandle8f24432005-10-03 14:16:44 +0000109 return True
110 for ext in ".exe", ".bat":
111 if os.path.isfile(cmd + ext):
112 return True
113 return False
114else:
115 def _isexecutable(cmd):
116 if os.path.isfile(cmd):
117 mode = os.stat(cmd)[stat.ST_MODE]
118 if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
119 return True
120 return False
121
Fred Drake3f8f1642001-07-19 03:46:26 +0000122def _iscommand(cmd):
Georg Brandle8f24432005-10-03 14:16:44 +0000123 """Return True if cmd is executable or can be found on the executable
124 search path."""
125 if _isexecutable(cmd):
126 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000127 path = os.environ.get("PATH")
128 if not path:
Tim Petersbc0e9102002-04-04 22:55:58 +0000129 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000130 for d in path.split(os.pathsep):
131 exe = os.path.join(d, cmd)
Georg Brandle8f24432005-10-03 14:16:44 +0000132 if _isexecutable(exe):
Tim Petersbc0e9102002-04-04 22:55:58 +0000133 return True
134 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000135
136
Georg Brandle8f24432005-10-03 14:16:44 +0000137# General parent classes
138
139class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000140 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000141
Georg Brandl23929f22006-01-20 21:03:35 +0000142 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000143
Georg Brandle8f24432005-10-03 14:16:44 +0000144 def __init__(self, name=""):
145 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000146 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000147
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000148 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000149 raise NotImplementedError
150
Georg Brandle8f24432005-10-03 14:16:44 +0000151 def open_new(self, url):
152 return self.open(url, 1)
153
154 def open_new_tab(self, url):
155 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000156
157
Georg Brandle8f24432005-10-03 14:16:44 +0000158class GenericBrowser(BaseBrowser):
159 """Class for all browsers started with a command
160 and without remote functionality."""
161
Georg Brandl23929f22006-01-20 21:03:35 +0000162 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000163 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000164 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000165 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000166 else:
167 # name should be a list with arguments
168 self.name = name[0]
169 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000170 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000171
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000172 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000173 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000174 for arg in self.args]
175 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000176 if sys.platform[:3] == 'win':
177 p = subprocess.Popen(cmdline)
178 else:
179 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000180 return not p.wait()
181 except OSError:
182 return False
183
184
185class BackgroundBrowser(GenericBrowser):
186 """Class for all browsers which are to be started in the
187 background."""
188
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000189 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000190 cmdline = [self.name] + [arg.replace("%s", url)
191 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000192 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000193 if sys.platform[:3] == 'win':
194 p = subprocess.Popen(cmdline)
195 else:
196 setsid = getattr(os, 'setsid', None)
197 if not setsid:
198 setsid = getattr(os, 'setpgrp', None)
199 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
Georg Brandl23929f22006-01-20 21:03:35 +0000200 return (p.poll() is None)
201 except OSError:
202 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000203
204
Georg Brandle8f24432005-10-03 14:16:44 +0000205class UnixBrowser(BaseBrowser):
206 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000207
Georg Brandle8f24432005-10-03 14:16:44 +0000208 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400209 background = False
210 redirect_stdout = True
211 # In remote_args, %s will be replaced with the requested URL. %action will
212 # be replaced depending on the value of 'new' passed to open.
213 # remote_action is used for new=0 (open). If newwin is not None, it is
214 # used for new=1 (open_new). If newtab is not None, it is used for
215 # new=3 (open_new_tab). After both substitutions are made, any empty
216 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000217 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000218 remote_action = None
219 remote_action_newwin = None
220 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000221
Georg Brandl23929f22006-01-20 21:03:35 +0000222 def _invoke(self, args, remote, autoraise):
223 raise_opt = []
224 if remote and self.raise_opts:
225 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000226 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000227 opt = self.raise_opts[autoraise]
228 if opt: raise_opt = [opt]
229
230 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000231
Georg Brandl23929f22006-01-20 21:03:35 +0000232 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400233 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000234 else:
235 # for TTY browsers, we need stdin/out
236 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000237 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
238 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400239 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000240 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200241 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000242 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200243 try:
244 rc = p.wait(5)
245 # if remote call failed, open() will try direct invocation
246 return not rc
247 except subprocess.TimeoutExpired:
248 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000249 elif self.background:
250 if p.poll() is None:
251 return True
252 else:
253 return False
254 else:
255 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000256
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000257 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000258 if new == 0:
259 action = self.remote_action
260 elif new == 1:
261 action = self.remote_action_newwin
262 elif new == 2:
263 if self.remote_action_newtab is None:
264 action = self.remote_action_newwin
265 else:
266 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000267 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000268 raise Error("Bad 'new' parameter to open(); " +
269 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000270
Georg Brandl23929f22006-01-20 21:03:35 +0000271 args = [arg.replace("%s", url).replace("%action", action)
272 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400273 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000274 success = self._invoke(args, True, autoraise)
275 if not success:
276 # remote invocation failed, try straight way
277 args = [arg.replace("%s", url) for arg in self.args]
278 return self._invoke(args, False, False)
279 else:
280 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000281
282
Georg Brandle8f24432005-10-03 14:16:44 +0000283class Mozilla(UnixBrowser):
284 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000285
Georg Brandl23929f22006-01-20 21:03:35 +0000286 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000287 remote_args = ['-remote', 'openURL(%s%action)']
288 remote_action = ""
289 remote_action_newwin = ",new-window"
290 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000291 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000292
Georg Brandle8f24432005-10-03 14:16:44 +0000293Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000294
295
Georg Brandle8f24432005-10-03 14:16:44 +0000296class Galeon(UnixBrowser):
297 """Launcher class for Galeon/Epiphany browsers."""
298
Georg Brandl23929f22006-01-20 21:03:35 +0000299 raise_opts = ["-noraise", ""]
300 remote_args = ['%action', '%s']
301 remote_action = "-n"
302 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000303 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000304
305
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800306class Chrome(UnixBrowser):
307 "Launcher class for Google Chrome browser."
308
309 remote_args = ['%action', '%s']
310 remote_action = ""
311 remote_action_newwin = "--new-window"
312 remote_action_newtab = ""
313 background = True
314
315Chromium = Chrome
316
317
Georg Brandle8f24432005-10-03 14:16:44 +0000318class Opera(UnixBrowser):
319 "Launcher class for Opera browser."
320
Terry Reedydad532f2010-12-28 19:30:19 +0000321 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000322 remote_args = ['-remote', 'openURL(%s%action)']
323 remote_action = ""
324 remote_action_newwin = ",new-window"
325 remote_action_newtab = ",new-page"
326 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000327
328
329class Elinks(UnixBrowser):
330 "Launcher class for Elinks browsers."
331
Georg Brandl23929f22006-01-20 21:03:35 +0000332 remote_args = ['-remote', 'openURL(%s%action)']
333 remote_action = ""
334 remote_action_newwin = ",new-window"
335 remote_action_newtab = ",new-tab"
336 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000337
Georg Brandl23929f22006-01-20 21:03:35 +0000338 # elinks doesn't like its stdout to be redirected -
339 # it uses redirected stdout as a signal to do -dump
340 redirect_stdout = False
341
342
343class Konqueror(BaseBrowser):
344 """Controller for the KDE File Manager (kfm, or Konqueror).
345
346 See the output of ``kfmclient --commands``
347 for more information on the Konqueror remote-control interface.
348 """
349
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000350 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000351 # XXX Currently I know no way to prevent KFM from opening a new win.
352 if new == 2:
353 action = "newTab"
354 else:
355 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000356
R David Murray02ca1442012-09-03 12:44:29 -0400357 devnull = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000358 # if possible, put browser in separate process group, so
359 # keyboard interrupts don't affect browser as well as Python
360 setsid = getattr(os, 'setsid', None)
361 if not setsid:
362 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000363
Georg Brandl23929f22006-01-20 21:03:35 +0000364 try:
365 p = subprocess.Popen(["kfmclient", action, url],
366 close_fds=True, stdin=devnull,
367 stdout=devnull, stderr=devnull)
368 except OSError:
369 # fall through to next variant
370 pass
371 else:
372 p.wait()
373 # kfmclient's return code unfortunately has no meaning as it seems
374 return True
375
376 try:
377 p = subprocess.Popen(["konqueror", "--silent", url],
378 close_fds=True, stdin=devnull,
379 stdout=devnull, stderr=devnull,
380 preexec_fn=setsid)
381 except OSError:
382 # fall through to next variant
383 pass
384 else:
385 if p.poll() is None:
386 # Should be running now.
387 return True
Tim Peters887c0802006-01-20 23:40:56 +0000388
Georg Brandl23929f22006-01-20 21:03:35 +0000389 try:
390 p = subprocess.Popen(["kfm", "-d", url],
391 close_fds=True, stdin=devnull,
392 stdout=devnull, stderr=devnull,
393 preexec_fn=setsid)
394 except OSError:
395 return False
396 else:
397 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000398
399
400class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000401 # There should be a way to maintain a connection to Grail, but the
402 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000403 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000404 def _find_grail_rc(self):
405 import glob
406 import pwd
407 import socket
408 import tempfile
409 tempdir = os.path.join(tempfile.gettempdir(),
410 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000411 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000412 filename = os.path.join(tempdir, user + "-*")
413 maybes = glob.glob(filename)
414 if not maybes:
415 return None
416 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
417 for fn in maybes:
418 # need to PING each one until we find one that's live
419 try:
420 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200421 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000422 # no good; attempt to clean it out, but don't fail:
423 try:
424 os.unlink(fn)
425 except IOError:
426 pass
427 else:
428 return s
429
430 def _remote(self, action):
431 s = self._find_grail_rc()
432 if not s:
433 return 0
434 s.send(action)
435 s.close()
436 return 1
437
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000438 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000439 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000440 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000441 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000442 ok = self._remote("LOAD " + url)
443 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000444
Fred Drakec70b4482000-07-09 16:45:56 +0000445
Tim Peters658cba62001-02-09 20:06:00 +0000446#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000447# Platform support for Unix
448#
Fred Drakec70b4482000-07-09 16:45:56 +0000449
Georg Brandle8f24432005-10-03 14:16:44 +0000450# These are the right tests because all these Unix browsers require either
451# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000452
Neal Norwitz196f7332005-10-04 03:17:49 +0000453def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000454
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200455 # use xdg-open if around
456 if _iscommand("xdg-open"):
457 register("xdg-open", None, BackgroundBrowser("xdg-open"))
458
459 # The default GNOME3 browser
460 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gvfs-open"):
461 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
462
Guido van Rossumd8faa362007-04-27 19:54:29 +0000463 # The default GNOME browser
464 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):
465 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000466
Guido van Rossumd8faa362007-04-27 19:54:29 +0000467 # The default KDE browser
468 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
469 register("kfmclient", Konqueror, Konqueror("kfmclient"))
470
471 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000472 for browser in ("mozilla-firefox", "firefox",
473 "mozilla-firebird", "firebird",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000474 "seamonkey", "mozilla", "netscape"):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000475 if _iscommand(browser):
476 register(browser, None, Mozilla(browser))
477
Georg Brandle8f24432005-10-03 14:16:44 +0000478 # Konqueror/kfm, the KDE browser.
Georg Brandlb9801132005-10-08 20:47:38 +0000479 if _iscommand("kfm"):
480 register("kfm", Konqueror, Konqueror("kfm"))
481 elif _iscommand("konqueror"):
482 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000483
Georg Brandle8f24432005-10-03 14:16:44 +0000484 # Gnome's Galeon and Epiphany
485 for browser in ("galeon", "epiphany"):
486 if _iscommand(browser):
487 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000488
Georg Brandle8f24432005-10-03 14:16:44 +0000489 # Skipstone, another Gtk/Mozilla based browser
490 if _iscommand("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000491 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000492
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800493 # Google Chrome/Chromium browsers
494 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
495 if _iscommand(browser):
496 register(browser, None, Chrome(browser))
497
Georg Brandle8f24432005-10-03 14:16:44 +0000498 # Opera, quite popular
499 if _iscommand("opera"):
500 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000501
Georg Brandle8f24432005-10-03 14:16:44 +0000502 # Next, Mosaic -- old but still in use.
503 if _iscommand("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000504 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000505
Georg Brandle8f24432005-10-03 14:16:44 +0000506 # Grail, the Python browser. Does anybody still use it?
507 if _iscommand("grail"):
508 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000509
Neal Norwitz196f7332005-10-04 03:17:49 +0000510# Prefer X browsers if present
511if os.environ.get("DISPLAY"):
512 register_X_browsers()
513
Georg Brandle8f24432005-10-03 14:16:44 +0000514# Also try console browsers
515if os.environ.get("TERM"):
516 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
517 if _iscommand("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000518 register("links", None, GenericBrowser("links"))
Georg Brandle8f24432005-10-03 14:16:44 +0000519 if _iscommand("elinks"):
520 register("elinks", None, Elinks("elinks"))
521 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
522 if _iscommand("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000523 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000524 # The w3m browser <http://w3m.sourceforge.net/>
525 if _iscommand("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000526 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000527
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000528#
529# Platform support for Windows
530#
Fred Drakec70b4482000-07-09 16:45:56 +0000531
532if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000533 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000534 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000535 try:
536 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200537 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000538 # [Error 22] No application is associated with the specified
539 # file for this operation: '<URL>'
540 return False
541 else:
542 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000543
544 _tryorder = []
545 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000546
547 # First try to use the default Windows browser
548 register("windows-default", WindowsDefault)
549
550 # Detect some common Windows browsers, fallback to IE
551 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
552 "Internet Explorer\\IEXPLORE.EXE")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000553 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
Guido van Rossumd8faa362007-04-27 19:54:29 +0000554 "netscape", "opera", iexplore):
Georg Brandle8f24432005-10-03 14:16:44 +0000555 if _iscommand(browser):
Georg Brandl23929f22006-01-20 21:03:35 +0000556 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000557
Fred Drakec70b4482000-07-09 16:45:56 +0000558#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000559# Platform support for MacOS
560#
Fred Drakec70b4482000-07-09 16:45:56 +0000561
Georg Brandle8f24432005-10-03 14:16:44 +0000562if sys.platform == 'darwin':
563 # Adapted from patch submitted to SourceForge by Steven J. Burr
564 class MacOSX(BaseBrowser):
565 """Launcher class for Aqua browsers on Mac OS X
566
567 Optionally specify a browser name on instantiation. Note that this
568 will not work for Aqua browsers if the user has moved the application
569 package after installation.
570
571 If no browser is specified, the default browser, as specified in the
572 Internet System Preferences panel, will be used.
573 """
574 def __init__(self, name):
575 self.name = name
576
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000577 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000578 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000579 # hack for local urls
580 if not ':' in url:
581 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000582
Georg Brandle8f24432005-10-03 14:16:44 +0000583 # new must be 0 or 1
584 new = int(bool(new))
585 if self.name == "default":
586 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000587 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000588 else:
589 # User called get and chose a browser
590 if self.name == "OmniWeb":
591 toWindow = ""
592 else:
593 # Include toWindow parameter of OpenURL command for browsers
594 # that support it. 0 == new window; -1 == existing
595 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000596 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000597 script = '''tell application "%s"
598 activate
599 %s %s
600 end tell''' % (self.name, cmd, toWindow)
601 # Open pipe to AppleScript through osascript command
602 osapipe = os.popen("osascript", "w")
603 if osapipe is None:
604 return False
605 # Write script to osascript's stdin
606 osapipe.write(script)
607 rc = osapipe.close()
608 return not rc
609
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000610 class MacOSXOSAScript(BaseBrowser):
611 def __init__(self, name):
612 self._name = name
613
614 def open(self, url, new=0, autoraise=True):
615 if self._name == 'default':
616 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
617 else:
618 script = '''
619 tell application "%s"
620 activate
621 open location "%s"
622 end
623 '''%(self._name, url.replace('"', '%22'))
624
625 osapipe = os.popen("osascript", "w")
626 if osapipe is None:
627 return False
628
629 osapipe.write(script)
630 rc = osapipe.close()
631 return not rc
632
633
Georg Brandle8f24432005-10-03 14:16:44 +0000634 # Don't clear _tryorder or _browsers since OS X can use above Unix support
635 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000636 register("safari", None, MacOSXOSAScript('safari'), -1)
637 register("firefox", None, MacOSXOSAScript('firefox'), -1)
638 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000639
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000640
641# OK, now that we know what the default preference orders for each
642# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000643if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000644 _userchoices = os.environ["BROWSER"].split(os.pathsep)
645 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000646
Georg Brandle8f24432005-10-03 14:16:44 +0000647 # Treat choices in same way as if passed into get() but do register
648 # and prepend to _tryorder
649 for cmdline in _userchoices:
650 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000651 cmd = _synthesize(cmdline, -1)
652 if cmd[1] is None:
653 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000654 cmdline = None # to make del work if _userchoices was empty
655 del cmdline
656 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000657
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000658# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000659
660
661def main():
662 import getopt
663 usage = """Usage: %s [-n | -t] url
664 -n: open new window
665 -t: open new tab""" % sys.argv[0]
666 try:
667 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000668 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000669 print(msg, file=sys.stderr)
670 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000671 sys.exit(1)
672 new_win = 0
673 for o, a in opts:
674 if o == '-n': new_win = 1
675 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000676 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000677 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000678 sys.exit(1)
679
680 url = args[0]
681 open(url, new_win)
682
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000683 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000684
Georg Brandle8f24432005-10-03 14:16:44 +0000685if __name__ == "__main__":
686 main()