blob: e369acb1df64a623119ce220b4b887adf0a12e91 [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
Georg Brandl23929f22006-01-20 21:03:35 +0000209 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000210 remote_action = None
211 remote_action_newwin = None
212 remote_action_newtab = None
Georg Brandl23929f22006-01-20 21:03:35 +0000213 background = False
214 redirect_stdout = True
Georg Brandle8f24432005-10-03 14:16:44 +0000215
Georg Brandl23929f22006-01-20 21:03:35 +0000216 def _invoke(self, args, remote, autoraise):
217 raise_opt = []
218 if remote and self.raise_opts:
219 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000220 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000221 opt = self.raise_opts[autoraise]
222 if opt: raise_opt = [opt]
223
224 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000225
Georg Brandl23929f22006-01-20 21:03:35 +0000226 if remote or self.background:
Amaury Forgeot d'Arcbc2ce572008-12-05 01:02:21 +0000227 inout = io.open(os.devnull, "r+")
Georg Brandl23929f22006-01-20 21:03:35 +0000228 else:
229 # for TTY browsers, we need stdin/out
230 inout = None
231 # if possible, put browser in separate process group, so
232 # keyboard interrupts don't affect browser as well as Python
233 setsid = getattr(os, 'setsid', None)
234 if not setsid:
235 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000236
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),
239 stderr=inout, preexec_fn=setsid)
240 if remote:
241 # wait five secons. If the subprocess is not finished, the
242 # remote invocation has (hopefully) started a new instance.
243 time.sleep(1)
244 rc = p.poll()
245 if rc is None:
246 time.sleep(4)
247 rc = p.poll()
248 if rc is None:
249 return True
250 # if remote call failed, open() will try direct invocation
251 return not rc
252 elif self.background:
253 if p.poll() is None:
254 return True
255 else:
256 return False
257 else:
258 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000259
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000260 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000261 if new == 0:
262 action = self.remote_action
263 elif new == 1:
264 action = self.remote_action_newwin
265 elif new == 2:
266 if self.remote_action_newtab is None:
267 action = self.remote_action_newwin
268 else:
269 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000270 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000271 raise Error("Bad 'new' parameter to open(); " +
272 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000273
Georg Brandl23929f22006-01-20 21:03:35 +0000274 args = [arg.replace("%s", url).replace("%action", action)
275 for arg in self.remote_args]
276 success = self._invoke(args, True, autoraise)
277 if not success:
278 # remote invocation failed, try straight way
279 args = [arg.replace("%s", url) for arg in self.args]
280 return self._invoke(args, False, False)
281 else:
282 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000283
284
Georg Brandle8f24432005-10-03 14:16:44 +0000285class Mozilla(UnixBrowser):
286 """Launcher class for Mozilla/Netscape browsers."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000287
Georg Brandl23929f22006-01-20 21:03:35 +0000288 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000289 remote_args = ['-remote', 'openURL(%s%action)']
290 remote_action = ""
291 remote_action_newwin = ",new-window"
292 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000293 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000294
Georg Brandle8f24432005-10-03 14:16:44 +0000295Netscape = Mozilla
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000296
297
Georg Brandle8f24432005-10-03 14:16:44 +0000298class Galeon(UnixBrowser):
299 """Launcher class for Galeon/Epiphany browsers."""
300
Georg Brandl23929f22006-01-20 21:03:35 +0000301 raise_opts = ["-noraise", ""]
302 remote_args = ['%action', '%s']
303 remote_action = "-n"
304 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000305 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000306
307
Georg Brandle8f24432005-10-03 14:16:44 +0000308class Opera(UnixBrowser):
309 "Launcher class for Opera browser."
310
Terry Reedydad532f2010-12-28 19:30:19 +0000311 raise_opts = ["-noraise", ""]
Georg Brandl23929f22006-01-20 21:03:35 +0000312 remote_args = ['-remote', 'openURL(%s%action)']
313 remote_action = ""
314 remote_action_newwin = ",new-window"
315 remote_action_newtab = ",new-page"
316 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000317
318
319class Elinks(UnixBrowser):
320 "Launcher class for Elinks browsers."
321
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-tab"
326 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000327
Georg Brandl23929f22006-01-20 21:03:35 +0000328 # elinks doesn't like its stdout to be redirected -
329 # it uses redirected stdout as a signal to do -dump
330 redirect_stdout = False
331
332
333class Konqueror(BaseBrowser):
334 """Controller for the KDE File Manager (kfm, or Konqueror).
335
336 See the output of ``kfmclient --commands``
337 for more information on the Konqueror remote-control interface.
338 """
339
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000340 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000341 # XXX Currently I know no way to prevent KFM from opening a new win.
342 if new == 2:
343 action = "newTab"
344 else:
345 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000346
Amaury Forgeot d'Arc2b2b44d2008-05-12 14:41:00 +0000347 devnull = io.open(os.devnull, "r+")
Georg Brandl23929f22006-01-20 21:03:35 +0000348 # if possible, put browser in separate process group, so
349 # keyboard interrupts don't affect browser as well as Python
350 setsid = getattr(os, 'setsid', None)
351 if not setsid:
352 setsid = getattr(os, 'setpgrp', None)
Tim Peters887c0802006-01-20 23:40:56 +0000353
Georg Brandl23929f22006-01-20 21:03:35 +0000354 try:
355 p = subprocess.Popen(["kfmclient", action, url],
356 close_fds=True, stdin=devnull,
357 stdout=devnull, stderr=devnull)
358 except OSError:
359 # fall through to next variant
360 pass
361 else:
362 p.wait()
363 # kfmclient's return code unfortunately has no meaning as it seems
364 return True
365
366 try:
367 p = subprocess.Popen(["konqueror", "--silent", url],
368 close_fds=True, stdin=devnull,
369 stdout=devnull, stderr=devnull,
370 preexec_fn=setsid)
371 except OSError:
372 # fall through to next variant
373 pass
374 else:
375 if p.poll() is None:
376 # Should be running now.
377 return True
Tim Peters887c0802006-01-20 23:40:56 +0000378
Georg Brandl23929f22006-01-20 21:03:35 +0000379 try:
380 p = subprocess.Popen(["kfm", "-d", url],
381 close_fds=True, stdin=devnull,
382 stdout=devnull, stderr=devnull,
383 preexec_fn=setsid)
384 except OSError:
385 return False
386 else:
387 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000388
389
390class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000391 # There should be a way to maintain a connection to Grail, but the
392 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000393 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000394 def _find_grail_rc(self):
395 import glob
396 import pwd
397 import socket
398 import tempfile
399 tempdir = os.path.join(tempfile.gettempdir(),
400 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000401 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000402 filename = os.path.join(tempdir, user + "-*")
403 maybes = glob.glob(filename)
404 if not maybes:
405 return None
406 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
407 for fn in maybes:
408 # need to PING each one until we find one that's live
409 try:
410 s.connect(fn)
411 except socket.error:
412 # no good; attempt to clean it out, but don't fail:
413 try:
414 os.unlink(fn)
415 except IOError:
416 pass
417 else:
418 return s
419
420 def _remote(self, action):
421 s = self._find_grail_rc()
422 if not s:
423 return 0
424 s.send(action)
425 s.close()
426 return 1
427
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000428 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000429 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000430 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000431 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000432 ok = self._remote("LOAD " + url)
433 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000434
Fred Drakec70b4482000-07-09 16:45:56 +0000435
Tim Peters658cba62001-02-09 20:06:00 +0000436#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000437# Platform support for Unix
438#
Fred Drakec70b4482000-07-09 16:45:56 +0000439
Georg Brandle8f24432005-10-03 14:16:44 +0000440# These are the right tests because all these Unix browsers require either
441# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000442
Neal Norwitz196f7332005-10-04 03:17:49 +0000443def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000444
Guido van Rossumd8faa362007-04-27 19:54:29 +0000445 # The default GNOME browser
446 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):
447 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000448
Guido van Rossumd8faa362007-04-27 19:54:29 +0000449 # The default KDE browser
450 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
451 register("kfmclient", Konqueror, Konqueror("kfmclient"))
452
453 # The Mozilla/Netscape browsers
Georg Brandl4a5a9182005-11-22 19:18:01 +0000454 for browser in ("mozilla-firefox", "firefox",
455 "mozilla-firebird", "firebird",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000456 "seamonkey", "mozilla", "netscape"):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000457 if _iscommand(browser):
458 register(browser, None, Mozilla(browser))
459
Georg Brandle8f24432005-10-03 14:16:44 +0000460 # Konqueror/kfm, the KDE browser.
Georg Brandlb9801132005-10-08 20:47:38 +0000461 if _iscommand("kfm"):
462 register("kfm", Konqueror, Konqueror("kfm"))
463 elif _iscommand("konqueror"):
464 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000465
Georg Brandle8f24432005-10-03 14:16:44 +0000466 # Gnome's Galeon and Epiphany
467 for browser in ("galeon", "epiphany"):
468 if _iscommand(browser):
469 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000470
Georg Brandle8f24432005-10-03 14:16:44 +0000471 # Skipstone, another Gtk/Mozilla based browser
472 if _iscommand("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000473 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000474
Georg Brandle8f24432005-10-03 14:16:44 +0000475 # Opera, quite popular
476 if _iscommand("opera"):
477 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000478
Georg Brandle8f24432005-10-03 14:16:44 +0000479 # Next, Mosaic -- old but still in use.
480 if _iscommand("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000481 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000482
Georg Brandle8f24432005-10-03 14:16:44 +0000483 # Grail, the Python browser. Does anybody still use it?
484 if _iscommand("grail"):
485 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000486
Neal Norwitz196f7332005-10-04 03:17:49 +0000487# Prefer X browsers if present
488if os.environ.get("DISPLAY"):
489 register_X_browsers()
490
Georg Brandle8f24432005-10-03 14:16:44 +0000491# Also try console browsers
492if os.environ.get("TERM"):
493 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
494 if _iscommand("links"):
Georg Brandl23929f22006-01-20 21:03:35 +0000495 register("links", None, GenericBrowser("links"))
Georg Brandle8f24432005-10-03 14:16:44 +0000496 if _iscommand("elinks"):
497 register("elinks", None, Elinks("elinks"))
498 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
499 if _iscommand("lynx"):
Georg Brandl23929f22006-01-20 21:03:35 +0000500 register("lynx", None, GenericBrowser("lynx"))
Georg Brandle8f24432005-10-03 14:16:44 +0000501 # The w3m browser <http://w3m.sourceforge.net/>
502 if _iscommand("w3m"):
Georg Brandl23929f22006-01-20 21:03:35 +0000503 register("w3m", None, GenericBrowser("w3m"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000504
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000505#
506# Platform support for Windows
507#
Fred Drakec70b4482000-07-09 16:45:56 +0000508
509if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000510 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000511 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000512 try:
513 os.startfile(url)
514 except WindowsError:
515 # [Error 22] No application is associated with the specified
516 # file for this operation: '<URL>'
517 return False
518 else:
519 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000520
521 _tryorder = []
522 _browsers = {}
Guido van Rossumd8faa362007-04-27 19:54:29 +0000523
524 # First try to use the default Windows browser
525 register("windows-default", WindowsDefault)
526
527 # Detect some common Windows browsers, fallback to IE
528 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
529 "Internet Explorer\\IEXPLORE.EXE")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000530 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
Guido van Rossumd8faa362007-04-27 19:54:29 +0000531 "netscape", "opera", iexplore):
Georg Brandle8f24432005-10-03 14:16:44 +0000532 if _iscommand(browser):
Georg Brandl23929f22006-01-20 21:03:35 +0000533 register(browser, None, BackgroundBrowser(browser))
Fred Drakec70b4482000-07-09 16:45:56 +0000534
Fred Drakec70b4482000-07-09 16:45:56 +0000535#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000536# Platform support for MacOS
537#
Fred Drakec70b4482000-07-09 16:45:56 +0000538
Georg Brandle8f24432005-10-03 14:16:44 +0000539if sys.platform == 'darwin':
540 # Adapted from patch submitted to SourceForge by Steven J. Burr
541 class MacOSX(BaseBrowser):
542 """Launcher class for Aqua browsers on Mac OS X
543
544 Optionally specify a browser name on instantiation. Note that this
545 will not work for Aqua browsers if the user has moved the application
546 package after installation.
547
548 If no browser is specified, the default browser, as specified in the
549 Internet System Preferences panel, will be used.
550 """
551 def __init__(self, name):
552 self.name = name
553
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000554 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000555 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000556 # hack for local urls
557 if not ':' in url:
558 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000559
Georg Brandle8f24432005-10-03 14:16:44 +0000560 # new must be 0 or 1
561 new = int(bool(new))
562 if self.name == "default":
563 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000564 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000565 else:
566 # User called get and chose a browser
567 if self.name == "OmniWeb":
568 toWindow = ""
569 else:
570 # Include toWindow parameter of OpenURL command for browsers
571 # that support it. 0 == new window; -1 == existing
572 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000573 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000574 script = '''tell application "%s"
575 activate
576 %s %s
577 end tell''' % (self.name, cmd, toWindow)
578 # Open pipe to AppleScript through osascript command
579 osapipe = os.popen("osascript", "w")
580 if osapipe is None:
581 return False
582 # Write script to osascript's stdin
583 osapipe.write(script)
584 rc = osapipe.close()
585 return not rc
586
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000587 class MacOSXOSAScript(BaseBrowser):
588 def __init__(self, name):
589 self._name = name
590
591 def open(self, url, new=0, autoraise=True):
592 if self._name == 'default':
593 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
594 else:
595 script = '''
596 tell application "%s"
597 activate
598 open location "%s"
599 end
600 '''%(self._name, url.replace('"', '%22'))
601
602 osapipe = os.popen("osascript", "w")
603 if osapipe is None:
604 return False
605
606 osapipe.write(script)
607 rc = osapipe.close()
608 return not rc
609
610
Georg Brandle8f24432005-10-03 14:16:44 +0000611 # Don't clear _tryorder or _browsers since OS X can use above Unix support
612 # (but we prefer using the OS X specific stuff)
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000613 register("safari", None, MacOSXOSAScript('safari'), -1)
614 register("firefox", None, MacOSXOSAScript('firefox'), -1)
615 register("MacOSX", None, MacOSXOSAScript('default'), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000616
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000617
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000618#
619# Platform support for OS/2
620#
621
Georg Brandle8f24432005-10-03 14:16:44 +0000622if sys.platform[:3] == "os2" and _iscommand("netscape"):
623 _tryorder = []
624 _browsers = {}
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000625 register("os2netscape", None,
Georg Brandl23929f22006-01-20 21:03:35 +0000626 GenericBrowser(["start", "netscape", "%s"]), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000627
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000628
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000629# OK, now that we know what the default preference orders for each
630# platform are, allow user to override them with the BROWSER variable.
Raymond Hettinger54f02222002-06-01 14:18:47 +0000631if "BROWSER" in os.environ:
Georg Brandle8f24432005-10-03 14:16:44 +0000632 _userchoices = os.environ["BROWSER"].split(os.pathsep)
633 _userchoices.reverse()
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000634
Georg Brandle8f24432005-10-03 14:16:44 +0000635 # Treat choices in same way as if passed into get() but do register
636 # and prepend to _tryorder
637 for cmdline in _userchoices:
638 if cmdline != '':
Benjamin Peterson8719ad52009-09-11 22:24:02 +0000639 cmd = _synthesize(cmdline, -1)
640 if cmd[1] is None:
641 register(cmdline, None, GenericBrowser(cmdline), -1)
Georg Brandle8f24432005-10-03 14:16:44 +0000642 cmdline = None # to make del work if _userchoices was empty
643 del cmdline
644 del _userchoices
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000645
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000646# what to do if _tryorder is now empty?
Georg Brandle8f24432005-10-03 14:16:44 +0000647
648
649def main():
650 import getopt
651 usage = """Usage: %s [-n | -t] url
652 -n: open new window
653 -t: open new tab""" % sys.argv[0]
654 try:
655 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000656 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000657 print(msg, file=sys.stderr)
658 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000659 sys.exit(1)
660 new_win = 0
661 for o, a in opts:
662 if o == '-n': new_win = 1
663 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000664 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000665 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000666 sys.exit(1)
667
668 url = args[0]
669 open(url, new_win)
670
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000671 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000672
Georg Brandle8f24432005-10-03 14:16:44 +0000673if __name__ == "__main__":
674 main()