blob: 2714bd144e38d16f792f501bcfdad4a5f2450ea8 [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
Serhiy Storchaka540dcba2013-02-13 12:19:40 +02008import shutil
Fred Drakec70b4482000-07-09 16:45:56 +00009import sys
Georg Brandle8f24432005-10-03 14:16:44 +000010import stat
Georg Brandl23929f22006-01-20 21:03:35 +000011import subprocess
12import time
Fred Drakec70b4482000-07-09 16:45:56 +000013
Georg Brandle8f24432005-10-03 14:16:44 +000014__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
Skip Montanaro40fc1602001-03-01 04:27:19 +000015
Fred Drakec70b4482000-07-09 16:45:56 +000016class Error(Exception):
17 pass
18
Tim Peters658cba62001-02-09 20:06:00 +000019_browsers = {} # Dictionary of available browser controllers
20_tryorder = [] # Preference order of available browsers
Fred Drakec70b4482000-07-09 16:45:56 +000021
Georg Brandle8f24432005-10-03 14:16:44 +000022def register(name, klass, instance=None, update_tryorder=1):
Fred Drakec70b4482000-07-09 16:45:56 +000023 """Register a browser connector and, optionally, connection."""
24 _browsers[name.lower()] = [klass, instance]
Georg Brandle8f24432005-10-03 14:16:44 +000025 if update_tryorder > 0:
26 _tryorder.append(name)
27 elif update_tryorder < 0:
28 _tryorder.insert(0, name)
Fred Drakec70b4482000-07-09 16:45:56 +000029
Eric S. Raymondf7f18512001-01-23 13:16:32 +000030def get(using=None):
31 """Return a browser launcher instance appropriate for the environment."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000032 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000033 alternatives = [using]
34 else:
35 alternatives = _tryorder
36 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000037 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000038 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000039 browser = shlex.split(browser)
40 if browser[-1] == '&':
41 return BackgroundBrowser(browser[:-1])
42 else:
43 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000044 else:
Georg Brandle8f24432005-10-03 14:16:44 +000045 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000046 try:
47 command = _browsers[browser.lower()]
48 except KeyError:
49 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000050 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000051 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000052 elif command[0] is not None:
53 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000054 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000055
56# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000057# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
58# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000059
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000060def open(url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +000061 for name in _tryorder:
62 browser = get(name)
63 if browser.open(url, new, autoraise):
64 return True
65 return False
Fred Drakec70b4482000-07-09 16:45:56 +000066
Fred Drake3f8f1642001-07-19 03:46:26 +000067def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000068 return open(url, 1)
69
70def open_new_tab(url):
71 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000072
Fred Drakef4e5bd92001-04-12 22:07:27 +000073
Georg Brandle8f24432005-10-03 14:16:44 +000074def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000075 """Attempt to synthesize a controller base on existing controllers.
76
77 This is useful to create a controller when a user specifies a path to
78 an entry in the BROWSER environment variable -- we can copy a general
79 controller to operate using a specific installation of the desired
80 browser in this way.
81
82 If we can't create a controller in this way, or if there is no
83 executable for the requested browser, return [None, None].
84
85 """
Georg Brandle8f24432005-10-03 14:16:44 +000086 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +020087 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +000088 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +000089 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +000090 try:
91 command = _browsers[name.lower()]
92 except KeyError:
93 return [None, None]
94 # now attempt to clone to fit the new name:
95 controller = command[1]
96 if controller and name.lower() == controller.basename:
97 import copy
98 controller = copy.copy(controller)
99 controller.name = browser
100 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +0000101 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000102 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000103 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000104
Fred Drake3f8f1642001-07-19 03:46:26 +0000105
Georg Brandle8f24432005-10-03 14:16:44 +0000106# General parent classes
107
108class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000109 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000110
Georg Brandl23929f22006-01-20 21:03:35 +0000111 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000112
Georg Brandle8f24432005-10-03 14:16:44 +0000113 def __init__(self, name=""):
114 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000115 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000116
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000117 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000118 raise NotImplementedError
119
Georg Brandle8f24432005-10-03 14:16:44 +0000120 def open_new(self, url):
121 return self.open(url, 1)
122
123 def open_new_tab(self, url):
124 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000125
126
Georg Brandle8f24432005-10-03 14:16:44 +0000127class GenericBrowser(BaseBrowser):
128 """Class for all browsers started with a command
129 and without remote functionality."""
130
Georg Brandl23929f22006-01-20 21:03:35 +0000131 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000132 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000133 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000134 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000135 else:
136 # name should be a list with arguments
137 self.name = name[0]
138 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000139 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000140
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000141 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000142 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000143 for arg in self.args]
144 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000145 if sys.platform[:3] == 'win':
146 p = subprocess.Popen(cmdline)
147 else:
148 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000149 return not p.wait()
150 except OSError:
151 return False
152
153
154class BackgroundBrowser(GenericBrowser):
155 """Class for all browsers which are to be started in the
156 background."""
157
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000158 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000159 cmdline = [self.name] + [arg.replace("%s", url)
160 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000161 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000162 if sys.platform[:3] == 'win':
163 p = subprocess.Popen(cmdline)
164 else:
165 setsid = getattr(os, 'setsid', None)
166 if not setsid:
167 setsid = getattr(os, 'setpgrp', None)
168 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
Georg Brandl23929f22006-01-20 21:03:35 +0000169 return (p.poll() is None)
170 except OSError:
171 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000172
173
Georg Brandle8f24432005-10-03 14:16:44 +0000174class UnixBrowser(BaseBrowser):
175 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000176
Georg Brandle8f24432005-10-03 14:16:44 +0000177 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400178 background = False
179 redirect_stdout = True
180 # In remote_args, %s will be replaced with the requested URL. %action will
181 # be replaced depending on the value of 'new' passed to open.
182 # remote_action is used for new=0 (open). If newwin is not None, it is
183 # used for new=1 (open_new). If newtab is not None, it is used for
184 # new=3 (open_new_tab). After both substitutions are made, any empty
185 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000186 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000187 remote_action = None
188 remote_action_newwin = None
189 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000190
Georg Brandl23929f22006-01-20 21:03:35 +0000191 def _invoke(self, args, remote, autoraise):
192 raise_opt = []
193 if remote and self.raise_opts:
194 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000195 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000196 opt = self.raise_opts[autoraise]
197 if opt: raise_opt = [opt]
198
199 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000200
Georg Brandl23929f22006-01-20 21:03:35 +0000201 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400202 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000203 else:
204 # for TTY browsers, we need stdin/out
205 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000206 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
207 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400208 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000209 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200210 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000211 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200212 try:
213 rc = p.wait(5)
214 # if remote call failed, open() will try direct invocation
215 return not rc
216 except subprocess.TimeoutExpired:
217 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000218 elif self.background:
219 if p.poll() is None:
220 return True
221 else:
222 return False
223 else:
224 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000225
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000226 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000227 if new == 0:
228 action = self.remote_action
229 elif new == 1:
230 action = self.remote_action_newwin
231 elif new == 2:
232 if self.remote_action_newtab is None:
233 action = self.remote_action_newwin
234 else:
235 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000236 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000237 raise Error("Bad 'new' parameter to open(); " +
238 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000239
Georg Brandl23929f22006-01-20 21:03:35 +0000240 args = [arg.replace("%s", url).replace("%action", action)
241 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400242 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000243 success = self._invoke(args, True, autoraise)
244 if not success:
245 # remote invocation failed, try straight way
246 args = [arg.replace("%s", url) for arg in self.args]
247 return self._invoke(args, False, False)
248 else:
249 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000250
251
Georg Brandle8f24432005-10-03 14:16:44 +0000252class Mozilla(UnixBrowser):
253 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000254
Georg Brandl23929f22006-01-20 21:03:35 +0000255 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000256 remote_args = ['-remote', 'openURL(%s%action)']
257 remote_action = ""
258 remote_action_newwin = ",new-window"
259 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000260 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000261
Georg Brandle8f24432005-10-03 14:16:44 +0000262Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000263
264
Georg Brandle8f24432005-10-03 14:16:44 +0000265class Galeon(UnixBrowser):
266 """Launcher class for Galeon/Epiphany browsers."""
267
Georg Brandl23929f22006-01-20 21:03:35 +0000268 raise_opts = ["-noraise", ""]
269 remote_args = ['%action', '%s']
270 remote_action = "-n"
271 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000272 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000273
274
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800275class Chrome(UnixBrowser):
276 "Launcher class for Google Chrome browser."
277
278 remote_args = ['%action', '%s']
279 remote_action = ""
280 remote_action_newwin = "--new-window"
281 remote_action_newtab = ""
282 background = True
283
284Chromium = Chrome
285
286
Georg Brandle8f24432005-10-03 14:16:44 +0000287class Opera(UnixBrowser):
288 "Launcher class for Opera browser."
289
Terry Reedydad532f2010-12-28 19:30:19 +0000290 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000291 remote_args = ['-remote', 'openURL(%s%action)']
292 remote_action = ""
293 remote_action_newwin = ",new-window"
294 remote_action_newtab = ",new-page"
295 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000296
297
298class Elinks(UnixBrowser):
299 "Launcher class for Elinks browsers."
300
Georg Brandl23929f22006-01-20 21:03:35 +0000301 remote_args = ['-remote', 'openURL(%s%action)']
302 remote_action = ""
303 remote_action_newwin = ",new-window"
304 remote_action_newtab = ",new-tab"
305 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000306
Georg Brandl23929f22006-01-20 21:03:35 +0000307 # elinks doesn't like its stdout to be redirected -
308 # it uses redirected stdout as a signal to do -dump
309 redirect_stdout = False
310
311
312class Konqueror(BaseBrowser):
313 """Controller for the KDE File Manager (kfm, or Konqueror).
314
315 See the output of ``kfmclient --commands``
316 for more information on the Konqueror remote-control interface.
317 """
318
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000319 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000320 # XXX Currently I know no way to prevent KFM from opening a new win.
321 if new == 2:
322 action = "newTab"
323 else:
324 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000325
R David Murray02ca1442012-09-03 12:44:29 -0400326 devnull = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000327 # if possible, put browser in separate process group, so
328 # keyboard interrupts don't affect browser as well as Python
329 setsid = getattr(os, 'setsid', None)
330 if not setsid:
331 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000332
Georg Brandl23929f22006-01-20 21:03:35 +0000333 try:
334 p = subprocess.Popen(["kfmclient", action, url],
335 close_fds=True, stdin=devnull,
336 stdout=devnull, stderr=devnull)
337 except OSError:
338 # fall through to next variant
339 pass
340 else:
341 p.wait()
342 # kfmclient's return code unfortunately has no meaning as it seems
343 return True
344
345 try:
346 p = subprocess.Popen(["konqueror", "--silent", url],
347 close_fds=True, stdin=devnull,
348 stdout=devnull, stderr=devnull,
349 preexec_fn=setsid)
350 except OSError:
351 # fall through to next variant
352 pass
353 else:
354 if p.poll() is None:
355 # Should be running now.
356 return True
Tim Peters887c0802006-01-20 23:40:56 +0000357
Georg Brandl23929f22006-01-20 21:03:35 +0000358 try:
359 p = subprocess.Popen(["kfm", "-d", url],
360 close_fds=True, stdin=devnull,
361 stdout=devnull, stderr=devnull,
362 preexec_fn=setsid)
363 except OSError:
364 return False
365 else:
366 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000367
368
369class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000370 # There should be a way to maintain a connection to Grail, but the
371 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000372 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000373 def _find_grail_rc(self):
374 import glob
375 import pwd
376 import socket
377 import tempfile
378 tempdir = os.path.join(tempfile.gettempdir(),
379 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000380 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000381 filename = os.path.join(tempdir, user + "-*")
382 maybes = glob.glob(filename)
383 if not maybes:
384 return None
385 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
386 for fn in maybes:
387 # need to PING each one until we find one that's live
388 try:
389 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200390 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000391 # no good; attempt to clean it out, but don't fail:
392 try:
393 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200394 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000395 pass
396 else:
397 return s
398
399 def _remote(self, action):
400 s = self._find_grail_rc()
401 if not s:
402 return 0
403 s.send(action)
404 s.close()
405 return 1
406
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000407 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000408 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000409 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000410 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000411 ok = self._remote("LOAD " + url)
412 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000413
Fred Drakec70b4482000-07-09 16:45:56 +0000414
Tim Peters658cba62001-02-09 20:06:00 +0000415#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000416# Platform support for Unix
417#
Fred Drakec70b4482000-07-09 16:45:56 +0000418
Georg Brandle8f24432005-10-03 14:16:44 +0000419# These are the right tests because all these Unix browsers require either
420# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000421
Neal Norwitz196f7332005-10-04 03:17:49 +0000422def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000423
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200424 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200425 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200426 register("xdg-open", None, BackgroundBrowser("xdg-open"))
427
428 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200429 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200430 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
431
Guido van Rossumd8faa362007-04-27 19:54:29 +0000432 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200433 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000434 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000435
Guido van Rossumd8faa362007-04-27 19:54:29 +0000436 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200437 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000438 register("kfmclient", Konqueror, Konqueror("kfmclient"))
439
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100440 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100441 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
442
Guido van Rossumd8faa362007-04-27 19:54:29 +0000443 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000444 for browser in ("mozilla-firefox", "firefox",
445 "mozilla-firebird", "firebird",
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100446 "iceweasel", "iceape",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000447 "seamonkey", "mozilla", "netscape"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200448 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000449 register(browser, None, Mozilla(browser))
450
Georg Brandle8f24432005-10-03 14:16:44 +0000451 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200452 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000453 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200454 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000455 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000456
Georg Brandle8f24432005-10-03 14:16:44 +0000457 # Gnome's Galeon and Epiphany
458 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200459 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000460 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000461
Georg Brandle8f24432005-10-03 14:16:44 +0000462 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200463 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000464 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000465
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800466 # Google Chrome/Chromium browsers
467 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200468 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800469 register(browser, None, Chrome(browser))
470
Georg Brandle8f24432005-10-03 14:16:44 +0000471 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200472 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000473 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000474
Georg Brandle8f24432005-10-03 14:16:44 +0000475 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200476 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000477 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000478
Georg Brandle8f24432005-10-03 14:16:44 +0000479 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200480 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000481 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000482
Neal Norwitz196f7332005-10-04 03:17:49 +0000483# Prefer X browsers if present
484if os.environ.get("DISPLAY"):
485 register_X_browsers()
486
Georg Brandle8f24432005-10-03 14:16:44 +0000487# Also try console browsers
488if os.environ.get("TERM"):
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100489 if shutil.which("www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100490 register("www-browser", None, GenericBrowser("www-browser"))
Georg Brandle8f24432005-10-03 14:16:44 +0000491 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200492 if shutil.which("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000493 register("links", None, GenericBrowser("links"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200494 if shutil.which("elinks"):
Georg Brandle8f24432005-10-03 14:16:44 +0000495 register("elinks", None, Elinks("elinks"))
496 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200497 if shutil.which("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000498 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000499 # The w3m browser <http://w3m.sourceforge.net/>
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200500 if shutil.which("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000501 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000502
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000503#
504# Platform support for Windows
505#
Fred Drakec70b4482000-07-09 16:45:56 +0000506
507if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000508 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000509 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000510 try:
511 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200512 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000513 # [Error 22] No application is associated with the specified
514 # file for this operation: '<URL>'
515 return False
516 else:
517 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000518
519 _tryorder = []
520 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000521
522 # First try to use the default Windows browser
523 register("windows-default", WindowsDefault)
524
525 # Detect some common Windows browsers, fallback to IE
526 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
527 "Internet Explorer\\IEXPLORE.EXE")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000528 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
Guido van Rossumd8faa362007-04-27 19:54:29 +0000529 "netscape", "opera", iexplore):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200530 if shutil.which(browser):
Georg Brandl23929f22006-01-20 21:03:35 +0000531 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000532
Fred Drakec70b4482000-07-09 16:45:56 +0000533#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000534# Platform support for MacOS
535#
Fred Drakec70b4482000-07-09 16:45:56 +0000536
Georg Brandle8f24432005-10-03 14:16:44 +0000537if sys.platform == 'darwin':
538 # Adapted from patch submitted to SourceForge by Steven J. Burr
539 class MacOSX(BaseBrowser):
540 """Launcher class for Aqua browsers on Mac OS X
541
542 Optionally specify a browser name on instantiation. Note that this
543 will not work for Aqua browsers if the user has moved the application
544 package after installation.
545
546 If no browser is specified, the default browser, as specified in the
547 Internet System Preferences panel, will be used.
548 """
549 def __init__(self, name):
550 self.name = name
551
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000552 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000553 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000554 # hack for local urls
555 if not ':' in url:
556 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000557
Georg Brandle8f24432005-10-03 14:16:44 +0000558 # new must be 0 or 1
559 new = int(bool(new))
560 if self.name == "default":
561 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000562 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000563 else:
564 # User called get and chose a browser
565 if self.name == "OmniWeb":
566 toWindow = ""
567 else:
568 # Include toWindow parameter of OpenURL command for browsers
569 # that support it. 0 == new window; -1 == existing
570 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000571 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000572 script = '''tell application "%s"
573 activate
574 %s %s
575 end tell''' % (self.name, cmd, toWindow)
576 # Open pipe to AppleScript through osascript command
577 osapipe = os.popen("osascript", "w")
578 if osapipe is None:
579 return False
580 # Write script to osascript's stdin
581 osapipe.write(script)
582 rc = osapipe.close()
583 return not rc
584
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000585 class MacOSXOSAScript(BaseBrowser):
586 def __init__(self, name):
587 self._name = name
588
589 def open(self, url, new=0, autoraise=True):
590 if self._name == 'default':
591 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
592 else:
593 script = '''
594 tell application "%s"
595 activate
596 open location "%s"
597 end
598 '''%(self._name, url.replace('"', '%22'))
599
600 osapipe = os.popen("osascript", "w")
601 if osapipe is None:
602 return False
603
604 osapipe.write(script)
605 rc = osapipe.close()
606 return not rc
607
608
Georg Brandle8f24432005-10-03 14:16:44 +0000609 # Don't clear _tryorder or _browsers since OS X can use above Unix support
610 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000611 register("safari", None, MacOSXOSAScript('safari'), -1)
612 register("firefox", None, MacOSXOSAScript('firefox'), -1)
613 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000614
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000615
616# OK, now that we know what the default preference orders for each
617# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000618if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000619 _userchoices = os.environ["BROWSER"].split(os.pathsep)
620 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000621
Georg Brandle8f24432005-10-03 14:16:44 +0000622 # Treat choices in same way as if passed into get() but do register
623 # and prepend to _tryorder
624 for cmdline in _userchoices:
625 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000626 cmd = _synthesize(cmdline, -1)
627 if cmd[1] is None:
628 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000629 cmdline = None # to make del work if _userchoices was empty
630 del cmdline
631 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000632
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000633# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000634
635
636def main():
637 import getopt
638 usage = """Usage: %s [-n | -t] url
639 -n: open new window
640 -t: open new tab""" % sys.argv[0]
641 try:
642 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000643 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000644 print(msg, file=sys.stderr)
645 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000646 sys.exit(1)
647 new_win = 0
648 for o, a in opts:
649 if o == '-n': new_win = 1
650 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000651 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000652 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000653 sys.exit(1)
654
655 url = args[0]
656 open(url, new_win)
657
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000658 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000659
Georg Brandle8f24432005-10-03 14:16:44 +0000660if __name__ == "__main__":
661 main()