blob: 15eeb660e258318303739c83cf912e1fcb5d312b [file] [log] [blame]
Georg Brandle8f24432005-10-03 14:16:44 +00001#! /usr/bin/env python
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +00002"""Interfaces for launching and remotely controlling Web browsers."""
Georg Brandlee250302007-07-01 08:11:35 +00003# Maintained by Georg Brandl.
Fred Drakec70b4482000-07-09 16:45:56 +00004
5import os
Georg Brandl972ca502007-03-21 11:51:25 +00006import shlex
Fred Drakec70b4482000-07-09 16:45:56 +00007import sys
Georg Brandle8f24432005-10-03 14:16:44 +00008import stat
Georg Brandl23929f22006-01-20 21:03:35 +00009import subprocess
10import time
Fred Drakec70b4482000-07-09 16:45:56 +000011
Georg Brandle8f24432005-10-03 14:16:44 +000012__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
Skip Montanaro40fc1602001-03-01 04:27:19 +000013
Fred Drakec70b4482000-07-09 16:45:56 +000014class Error(Exception):
15 pass
16
Tim Peters658cba62001-02-09 20:06:00 +000017_browsers = {} # Dictionary of available browser controllers
18_tryorder = [] # Preference order of available browsers
Fred Drakec70b4482000-07-09 16:45:56 +000019
Georg Brandle8f24432005-10-03 14:16:44 +000020def register(name, klass, instance=None, update_tryorder=1):
Fred Drakec70b4482000-07-09 16:45:56 +000021 """Register a browser connector and, optionally, connection."""
22 _browsers[name.lower()] = [klass, instance]
Georg Brandle8f24432005-10-03 14:16:44 +000023 if update_tryorder > 0:
24 _tryorder.append(name)
25 elif update_tryorder < 0:
26 _tryorder.insert(0, name)
Fred Drakec70b4482000-07-09 16:45:56 +000027
Eric S. Raymondf7f18512001-01-23 13:16:32 +000028def get(using=None):
29 """Return a browser launcher instance appropriate for the environment."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000030 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000031 alternatives = [using]
32 else:
33 alternatives = _tryorder
34 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000035 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000036 # User gave us a command line, split it into name and args
Georg Brandl972ca502007-03-21 11:51:25 +000037 browser = shlex.split(browser)
38 if browser[-1] == '&':
39 return BackgroundBrowser(browser[:-1])
40 else:
41 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000042 else:
Georg Brandle8f24432005-10-03 14:16:44 +000043 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000044 try:
45 command = _browsers[browser.lower()]
46 except KeyError:
47 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000048 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000049 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000050 elif command[0] is not None:
51 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000052 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000053
54# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000055# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
56# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000057
Benjamin Peterson39186bc2009-07-23 14:19:08 +000058def open(url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +000059 for name in _tryorder:
60 browser = get(name)
61 if browser.open(url, new, autoraise):
62 return True
63 return False
Fred Drakec70b4482000-07-09 16:45:56 +000064
Fred Drake3f8f1642001-07-19 03:46:26 +000065def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000066 return open(url, 1)
67
68def open_new_tab(url):
69 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000070
Fred Drakef4e5bd92001-04-12 22:07:27 +000071
Georg Brandle8f24432005-10-03 14:16:44 +000072def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000073 """Attempt to synthesize a controller base on existing controllers.
74
75 This is useful to create a controller when a user specifies a path to
76 an entry in the BROWSER environment variable -- we can copy a general
77 controller to operate using a specific installation of the desired
78 browser in this way.
79
80 If we can't create a controller in this way, or if there is no
81 executable for the requested browser, return [None, None].
82
83 """
Georg Brandle8f24432005-10-03 14:16:44 +000084 cmd = browser.split()[0]
85 if not _iscommand(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +000086 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +000087 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +000088 try:
89 command = _browsers[name.lower()]
90 except KeyError:
91 return [None, None]
92 # now attempt to clone to fit the new name:
93 controller = command[1]
94 if controller and name.lower() == controller.basename:
95 import copy
96 controller = copy.copy(controller)
97 controller.name = browser
98 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000099 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000100 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000101 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000102
Fred Drake3f8f1642001-07-19 03:46:26 +0000103
Georg Brandle8f24432005-10-03 14:16:44 +0000104if sys.platform[:3] == "win":
105 def _isexecutable(cmd):
106 cmd = cmd.lower()
Georg Brandlb2afe852006-06-09 20:43:48 +0000107 if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):
Georg Brandle8f24432005-10-03 14:16:44 +0000108 return True
109 for ext in ".exe", ".bat":
110 if os.path.isfile(cmd + ext):
111 return True
112 return False
113else:
114 def _isexecutable(cmd):
115 if os.path.isfile(cmd):
116 mode = os.stat(cmd)[stat.ST_MODE]
117 if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
118 return True
119 return False
120
Fred Drake3f8f1642001-07-19 03:46:26 +0000121def _iscommand(cmd):
Georg Brandle8f24432005-10-03 14:16:44 +0000122 """Return True if cmd is executable or can be found on the executable
123 search path."""
124 if _isexecutable(cmd):
125 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000126 path = os.environ.get("PATH")
127 if not path:
Tim Petersbc0e9102002-04-04 22:55:58 +0000128 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000129 for d in path.split(os.pathsep):
130 exe = os.path.join(d, cmd)
Georg Brandle8f24432005-10-03 14:16:44 +0000131 if _isexecutable(exe):
Tim Petersbc0e9102002-04-04 22:55:58 +0000132 return True
133 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000134
135
Georg Brandle8f24432005-10-03 14:16:44 +0000136# General parent classes
137
138class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000139 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000140
Georg Brandl23929f22006-01-20 21:03:35 +0000141 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000142
Georg Brandle8f24432005-10-03 14:16:44 +0000143 def __init__(self, name=""):
144 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000145 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000146
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000147 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000148 raise NotImplementedError
149
Georg Brandle8f24432005-10-03 14:16:44 +0000150 def open_new(self, url):
151 return self.open(url, 1)
152
153 def open_new_tab(self, url):
154 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000155
156
Georg Brandle8f24432005-10-03 14:16:44 +0000157class GenericBrowser(BaseBrowser):
158 """Class for all browsers started with a command
159 and without remote functionality."""
160
Georg Brandl23929f22006-01-20 21:03:35 +0000161 def __init__(self, name):
162 if isinstance(name, basestring):
163 self.name = name
Georg Brandlee250302007-07-01 08:11:35 +0000164 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000165 else:
166 # name should be a list with arguments
167 self.name = name[0]
168 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000169 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000170
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000171 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000172 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000173 for arg in self.args]
174 try:
Georg Brandl2c94bf72006-09-24 10:36:01 +0000175 if sys.platform[:3] == 'win':
176 p = subprocess.Popen(cmdline)
177 else:
178 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000179 return not p.wait()
180 except OSError:
181 return False
182
183
184class BackgroundBrowser(GenericBrowser):
185 """Class for all browsers which are to be started in the
186 background."""
187
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000188 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000189 cmdline = [self.name] + [arg.replace("%s", url)
190 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000191 try:
Georg Brandl2c94bf72006-09-24 10:36:01 +0000192 if sys.platform[:3] == 'win':
193 p = subprocess.Popen(cmdline)
194 else:
195 setsid = getattr(os, 'setsid', None)
196 if not setsid:
197 setsid = getattr(os, 'setpgrp', None)
198 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
Georg Brandl23929f22006-01-20 21:03:35 +0000199 return (p.poll() is None)
200 except OSError:
201 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000202
203
Georg Brandle8f24432005-10-03 14:16:44 +0000204class UnixBrowser(BaseBrowser):
205 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000206
Georg Brandle8f24432005-10-03 14:16:44 +0000207 raise_opts = None
Georg Brandl23929f22006-01-20 21:03:35 +0000208 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000209 remote_action = None
210 remote_action_newwin = None
211 remote_action_newtab = None
Georg Brandl23929f22006-01-20 21:03:35 +0000212 background = False
213 redirect_stdout = True
Georg Brandle8f24432005-10-03 14:16:44 +0000214
Georg Brandl23929f22006-01-20 21:03:35 +0000215 def _invoke(self, args, remote, autoraise):
216 raise_opt = []
217 if remote and self.raise_opts:
218 # use autoraise argument only for remote invocation
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000219 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000220 opt = self.raise_opts[autoraise]
221 if opt: raise_opt = [opt]
222
223 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000224
Georg Brandl23929f22006-01-20 21:03:35 +0000225 if remote or self.background:
226 inout = file(os.devnull, "r+")
227 else:
228 # for TTY browsers, we need stdin/out
229 inout = None
230 # if possible, put browser in separate process group, so
231 # keyboard interrupts don't affect browser as well as Python
232 setsid = getattr(os, 'setsid', None)
233 if not setsid:
234 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000235
Georg Brandl23929f22006-01-20 21:03:35 +0000236 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
237 stdout=(self.redirect_stdout and inout or None),
238 stderr=inout, preexec_fn=setsid)
239 if remote:
Ezio Melotti56b26652011-07-21 11:18:18 +0300240 # wait five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000241 # remote invocation has (hopefully) started a new instance.
242 time.sleep(1)
243 rc = p.poll()
244 if rc is None:
245 time.sleep(4)
246 rc = p.poll()
247 if rc is None:
248 return True
249 # if remote call failed, open() will try direct invocation
250 return not rc
251 elif self.background:
252 if p.poll() is None:
253 return True
254 else:
255 return False
256 else:
257 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000258
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000259 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000260 if new == 0:
261 action = self.remote_action
262 elif new == 1:
263 action = self.remote_action_newwin
264 elif new == 2:
265 if self.remote_action_newtab is None:
266 action = self.remote_action_newwin
267 else:
268 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000269 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000270 raise Error("Bad 'new' parameter to open(); " +
271 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000272
Georg Brandl23929f22006-01-20 21:03:35 +0000273 args = [arg.replace("%s", url).replace("%action", action)
274 for arg in self.remote_args]
275 success = self._invoke(args, True, autoraise)
276 if not success:
277 # remote invocation failed, try straight way
278 args = [arg.replace("%s", url) for arg in self.args]
279 return self._invoke(args, False, False)
280 else:
281 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000282
283
Georg Brandle8f24432005-10-03 14:16:44 +0000284class Mozilla(UnixBrowser):
285 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000286
Georg Brandl23929f22006-01-20 21:03:35 +0000287 raise_opts = ["-noraise", "-raise"]
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-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000292 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000293
Georg Brandle8f24432005-10-03 14:16:44 +0000294Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000295
296
Georg Brandle8f24432005-10-03 14:16:44 +0000297class Galeon(UnixBrowser):
298 """Launcher class for Galeon/Epiphany browsers."""
299
Georg Brandl23929f22006-01-20 21:03:35 +0000300 raise_opts = ["-noraise", ""]
301 remote_args = ['%action', '%s']
302 remote_action = "-n"
303 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000304 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000305
306
doko@ubuntu.com66da7c52013-04-13 17:05:11 +0200307class Chrome(UnixBrowser):
308 "Launcher class for Google Chrome browser."
309
310 remote_args = ['%action', '%s']
311 remote_action = ""
312 remote_action_newwin = "--new-window"
313 remote_action_newtab = ""
314 background = True
315
316Chromium = Chrome
317
318
Georg Brandle8f24432005-10-03 14:16:44 +0000319class Opera(UnixBrowser):
320 "Launcher class for Opera browser."
321
Pablo Galindo8902a1d2018-07-07 00:54:09 +0100322 remote_args = ['%action', '%s']
Georg Brandl23929f22006-01-20 21:03:35 +0000323 remote_action = ""
Pablo Galindo8902a1d2018-07-07 00:54:09 +0100324 remote_action_newwin = "--new-window"
325 remote_action_newtab = ""
Georg Brandl23929f22006-01-20 21:03:35 +0000326 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
Benjamin Peterson39186bc2009-07-23 14:19:08 +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
Georg Brandl23929f22006-01-20 21:03:35 +0000357 devnull = file(os.devnull, "r+")
358 # 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)
421 except socket.error:
422 # 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
Benjamin Peterson39186bc2009-07-23 14:19:08 +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
doko@ubuntu.com66da7c52013-04-13 17:05:11 +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
Georg Brandl8f06d022007-03-16 07:55:09 +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
Georg Brandl8f06d022007-03-16 07:55:09 +0000467 # The default KDE browser
468 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
469 register("kfmclient", Konqueror, Konqueror("kfmclient"))
470
doko@ubuntu.com66da7c52013-04-13 17:05:11 +0200471 if _iscommand("x-www-browser"):
472 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
473
Georg Brandl8f06d022007-03-16 07:55:09 +0000474 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000475 for browser in ("mozilla-firefox", "firefox",
476 "mozilla-firebird", "firebird",
doko@ubuntu.com66da7c52013-04-13 17:05:11 +0200477 "iceweasel", "iceape",
Georg Brandl314acac2006-04-28 16:31:17 +0000478 "seamonkey", "mozilla", "netscape"):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000479 if _iscommand(browser):
480 register(browser, None, Mozilla(browser))
481
Georg Brandle8f24432005-10-03 14:16:44 +0000482 # Konqueror/kfm, the KDE browser.
Georg Brandlb9801132005-10-08 20:47:38 +0000483 if _iscommand("kfm"):
484 register("kfm", Konqueror, Konqueror("kfm"))
485 elif _iscommand("konqueror"):
486 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000487
Georg Brandle8f24432005-10-03 14:16:44 +0000488 # Gnome's Galeon and Epiphany
489 for browser in ("galeon", "epiphany"):
490 if _iscommand(browser):
491 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000492
Georg Brandle8f24432005-10-03 14:16:44 +0000493 # Skipstone, another Gtk/Mozilla based browser
494 if _iscommand("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000495 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000496
doko@ubuntu.com66da7c52013-04-13 17:05:11 +0200497 # Google Chrome/Chromium browsers
498 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
499 if _iscommand(browser):
500 register(browser, None, Chrome(browser))
501
Georg Brandle8f24432005-10-03 14:16:44 +0000502 # Opera, quite popular
503 if _iscommand("opera"):
504 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000505
Georg Brandle8f24432005-10-03 14:16:44 +0000506 # Next, Mosaic -- old but still in use.
507 if _iscommand("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000508 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000509
Georg Brandle8f24432005-10-03 14:16:44 +0000510 # Grail, the Python browser. Does anybody still use it?
511 if _iscommand("grail"):
512 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000513
Neal Norwitz196f7332005-10-04 03:17:49 +0000514# Prefer X browsers if present
515if os.environ.get("DISPLAY"):
516 register_X_browsers()
517
Georg Brandle8f24432005-10-03 14:16:44 +0000518# Also try console browsers
519if os.environ.get("TERM"):
doko@ubuntu.com66da7c52013-04-13 17:05:11 +0200520 if _iscommand("www-browser"):
521 register("www-browser", None, GenericBrowser("www-browser"))
Georg Brandle8f24432005-10-03 14:16:44 +0000522 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
523 if _iscommand("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000524 register("links", None, GenericBrowser("links"))
Georg Brandle8f24432005-10-03 14:16:44 +0000525 if _iscommand("elinks"):
526 register("elinks", None, Elinks("elinks"))
527 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
528 if _iscommand("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000529 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000530 # The w3m browser <http://w3m.sourceforge.net/>
531 if _iscommand("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000532 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000533
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000534#
535# Platform support for Windows
536#
Fred Drakec70b4482000-07-09 16:45:56 +0000537
538if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000539 class WindowsDefault(BaseBrowser):
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000540 def open(self, url, new=0, autoraise=True):
Georg Brandl8f06d022007-03-16 07:55:09 +0000541 try:
542 os.startfile(url)
543 except WindowsError:
544 # [Error 22] No application is associated with the specified
545 # file for this operation: '<URL>'
546 return False
547 else:
548 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000549
550 _tryorder = []
551 _browsers = {}
Georg Brandl8f06d022007-03-16 07:55:09 +0000552
553 # First try to use the default Windows browser
554 register("windows-default", WindowsDefault)
555
556 # Detect some common Windows browsers, fallback to IE
557 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
558 "Internet Explorer\\IEXPLORE.EXE")
Georg Brandl7377ad22006-05-03 17:46:13 +0000559 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
Georg Brandl8f06d022007-03-16 07:55:09 +0000560 "netscape", "opera", iexplore):
Georg Brandle8f24432005-10-03 14:16:44 +0000561 if _iscommand(browser):
Georg Brandl23929f22006-01-20 21:03:35 +0000562 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000563
Fred Drakec70b4482000-07-09 16:45:56 +0000564#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000565# Platform support for MacOS
566#
Fred Drakec70b4482000-07-09 16:45:56 +0000567
Georg Brandle8f24432005-10-03 14:16:44 +0000568if sys.platform == 'darwin':
569 # Adapted from patch submitted to SourceForge by Steven J. Burr
570 class MacOSX(BaseBrowser):
571 """Launcher class for Aqua browsers on Mac OS X
572
573 Optionally specify a browser name on instantiation. Note that this
574 will not work for Aqua browsers if the user has moved the application
575 package after installation.
576
577 If no browser is specified, the default browser, as specified in the
578 Internet System Preferences panel, will be used.
579 """
580 def __init__(self, name):
581 self.name = name
582
Benjamin Peterson39186bc2009-07-23 14:19:08 +0000583 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000584 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000585 # hack for local urls
586 if not ':' in url:
587 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000588
Georg Brandle8f24432005-10-03 14:16:44 +0000589 # new must be 0 or 1
590 new = int(bool(new))
591 if self.name == "default":
592 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000593 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000594 else:
595 # User called get and chose a browser
596 if self.name == "OmniWeb":
597 toWindow = ""
598 else:
599 # Include toWindow parameter of OpenURL command for browsers
600 # that support it. 0 == new window; -1 == existing
601 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000602 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000603 script = '''tell application "%s"
604 activate
605 %s %s
606 end tell''' % (self.name, cmd, toWindow)
607 # Open pipe to AppleScript through osascript command
608 osapipe = os.popen("osascript", "w")
609 if osapipe is None:
610 return False
611 # Write script to osascript's stdin
612 osapipe.write(script)
613 rc = osapipe.close()
614 return not rc
615
Ronald Oussoren4ef1c7e2010-05-02 09:48:21 +0000616 class MacOSXOSAScript(BaseBrowser):
617 def __init__(self, name):
618 self._name = name
619
620 def open(self, url, new=0, autoraise=True):
621 if self._name == 'default':
622 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
623 else:
624 script = '''
625 tell application "%s"
626 activate
627 open location "%s"
628 end
629 '''%(self._name, url.replace('"', '%22'))
630
631 osapipe = os.popen("osascript", "w")
632 if osapipe is None:
633 return False
634
635 osapipe.write(script)
636 rc = osapipe.close()
637 return not rc
638
639
Georg Brandle8f24432005-10-03 14:16:44 +0000640 # Don't clear _tryorder or _browsers since OS X can use above Unix support
641 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4ef1c7e2010-05-02 09:48:21 +0000642 register("safari", None, MacOSXOSAScript('safari'), -1)
643 register("firefox", None, MacOSXOSAScript('firefox'), -1)
Guido van Rossum5e2486b2016-10-13 13:29:55 -0700644 register("chrome", None, MacOSXOSAScript('chrome'), -1)
Ronald Oussorenb0153cf2010-05-02 09:55:57 +0000645 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000646
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000647
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000648#
649# Platform support for OS/2
650#
651
Georg Brandle8f24432005-10-03 14:16:44 +0000652if sys.platform[:3] == "os2" and _iscommand("netscape"):
653 _tryorder = []
654 _browsers = {}
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000655 register("os2netscape", None,
Georg Brandl23929f22006-01-20 21:03:35 +0000656 GenericBrowser(["start", "netscape", "%s"]), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000657
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000658
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000659# OK, now that we know what the default preference orders for each
660# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000661if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000662 _userchoices = os.environ["BROWSER"].split(os.pathsep)
663 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000664
Georg Brandle8f24432005-10-03 14:16:44 +0000665 # Treat choices in same way as if passed into get() but do register
666 # and prepend to _tryorder
667 for cmdline in _userchoices:
668 if cmdline != '':
Georg Brandl2e1308f2009-09-04 06:59:20 +0000669 cmd = _synthesize(cmdline, -1)
670 if cmd[1] is None:
671 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000672 cmdline = None # to make del work if _userchoices was empty
673 del cmdline
674 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000675
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000676# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000677
678
679def main():
680 import getopt
681 usage = """Usage: %s [-n | -t] url
682 -n: open new window
683 -t: open new tab""" % sys.argv[0]
684 try:
685 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
686 except getopt.error, msg:
687 print >>sys.stderr, msg
688 print >>sys.stderr, usage
689 sys.exit(1)
690 new_win = 0
691 for o, a in opts:
692 if o == '-n': new_win = 1
693 elif o == '-t': new_win = 2
Antoine Pitroub9d49632010-01-04 23:22:44 +0000694 if len(args) != 1:
Georg Brandle8f24432005-10-03 14:16:44 +0000695 print >>sys.stderr, usage
696 sys.exit(1)
697
698 url = args[0]
699 open(url, new_win)
700
Georg Brandl23929f22006-01-20 21:03:35 +0000701 print "\a"
702
Georg Brandle8f24432005-10-03 14:16:44 +0000703if __name__ == "__main__":
704 main()