blob: 2a5729b446f0cc0472e232e565f15c835da3d28f [file] [log] [blame]
Benjamin Peterson90f5ba52010-03-11 22:53:45 +00001#! /usr/bin/env python3
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +00002"""Interfaces for launching and remotely controlling Web browsers."""
Guido van Rossum992d4a32007-07-11 13:09:30 +00003# Maintained by Georg Brandl.
Fred Drakec70b4482000-07-09 16:45:56 +00004
5import os
Guido van Rossumd8faa362007-04-27 19:54:29 +00006import shlex
Serhiy Storchaka540dcba2013-02-13 12:19:40 +02007import shutil
Fred Drakec70b4482000-07-09 16:45:56 +00008import sys
Georg Brandl23929f22006-01-20 21:03:35 +00009import subprocess
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020010import threading
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
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020017_lock = threading.RLock()
David Steelee3ce6952017-02-24 23:47:38 -050018_browsers = {} # Dictionary of available browser controllers
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020019_tryorder = None # Preference order of available browsers
David Steelee3ce6952017-02-24 23:47:38 -050020_os_preferred_browser = None # The preferred browser
Fred Drakec70b4482000-07-09 16:45:56 +000021
David Steelee3ce6952017-02-24 23:47:38 -050022def register(name, klass, instance=None, *, preferred=False):
23 """Register a browser connector."""
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020024 with _lock:
25 if _tryorder is None:
26 register_standard_browsers()
27 _browsers[name.lower()] = [klass, instance]
David Steelee3ce6952017-02-24 23:47:38 -050028
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020029 # Preferred browsers go to the front of the list.
30 # Need to match to the default browser returned by xdg-settings, which
31 # may be of the form e.g. "firefox.desktop".
32 if preferred or (_os_preferred_browser and name in _os_preferred_browser):
33 _tryorder.insert(0, name)
34 else:
35 _tryorder.append(name)
Fred Drakec70b4482000-07-09 16:45:56 +000036
Eric S. Raymondf7f18512001-01-23 13:16:32 +000037def get(using=None):
38 """Return a browser launcher instance appropriate for the environment."""
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020039 if _tryorder is None:
40 with _lock:
41 if _tryorder is None:
42 register_standard_browsers()
Raymond Hettinger10ff7062002-06-02 03:04:52 +000043 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000044 alternatives = [using]
45 else:
46 alternatives = _tryorder
47 for browser in alternatives:
Raymond Hettingerbac788a2004-05-04 09:21:43 +000048 if '%s' in browser:
Georg Brandl23929f22006-01-20 21:03:35 +000049 # User gave us a command line, split it into name and args
Guido van Rossumd8faa362007-04-27 19:54:29 +000050 browser = shlex.split(browser)
51 if browser[-1] == '&':
52 return BackgroundBrowser(browser[:-1])
53 else:
54 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000055 else:
Georg Brandle8f24432005-10-03 14:16:44 +000056 # User gave us a browser name or path.
Fred Drakef4e5bd92001-04-12 22:07:27 +000057 try:
58 command = _browsers[browser.lower()]
59 except KeyError:
60 command = _synthesize(browser)
Georg Brandle8f24432005-10-03 14:16:44 +000061 if command[1] is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000062 return command[1]
Georg Brandle8f24432005-10-03 14:16:44 +000063 elif command[0] is not None:
64 return command[0]()
Eric S. Raymondf7f18512001-01-23 13:16:32 +000065 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000066
67# Please note: the following definition hides a builtin function.
Georg Brandle8f24432005-10-03 14:16:44 +000068# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
69# instead of "from webbrowser import *".
Fred Drakec70b4482000-07-09 16:45:56 +000070
Alexandre Vassalottie223eb82009-07-29 20:12:15 +000071def open(url, new=0, autoraise=True):
Serhiy Storchakaa7cba272017-03-08 17:15:54 +020072 if _tryorder is None:
73 with _lock:
74 if _tryorder is None:
75 register_standard_browsers()
Georg Brandle8f24432005-10-03 14:16:44 +000076 for name in _tryorder:
77 browser = get(name)
78 if browser.open(url, new, autoraise):
79 return True
80 return False
Fred Drakec70b4482000-07-09 16:45:56 +000081
Fred Drake3f8f1642001-07-19 03:46:26 +000082def open_new(url):
Georg Brandle8f24432005-10-03 14:16:44 +000083 return open(url, 1)
84
85def open_new_tab(url):
86 return open(url, 2)
Fred Drakec70b4482000-07-09 16:45:56 +000087
Fred Drakef4e5bd92001-04-12 22:07:27 +000088
Georg Brandle8f24432005-10-03 14:16:44 +000089def _synthesize(browser, update_tryorder=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +000090 """Attempt to synthesize a controller base on existing controllers.
91
92 This is useful to create a controller when a user specifies a path to
93 an entry in the BROWSER environment variable -- we can copy a general
94 controller to operate using a specific installation of the desired
95 browser in this way.
96
97 If we can't create a controller in this way, or if there is no
98 executable for the requested browser, return [None, None].
99
100 """
Georg Brandle8f24432005-10-03 14:16:44 +0000101 cmd = browser.split()[0]
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200102 if not shutil.which(cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000103 return [None, None]
Georg Brandle8f24432005-10-03 14:16:44 +0000104 name = os.path.basename(cmd)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000105 try:
106 command = _browsers[name.lower()]
107 except KeyError:
108 return [None, None]
109 # now attempt to clone to fit the new name:
110 controller = command[1]
111 if controller and name.lower() == controller.basename:
112 import copy
113 controller = copy.copy(controller)
114 controller.name = browser
115 controller.basename = os.path.basename(browser)
Georg Brandle8f24432005-10-03 14:16:44 +0000116 register(browser, None, controller, update_tryorder)
Fred Drakef4e5bd92001-04-12 22:07:27 +0000117 return [None, controller]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +0000118 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +0000119
Fred Drake3f8f1642001-07-19 03:46:26 +0000120
Georg Brandle8f24432005-10-03 14:16:44 +0000121# General parent classes
122
123class BaseBrowser(object):
Georg Brandl23929f22006-01-20 21:03:35 +0000124 """Parent class for all browsers. Do not use directly."""
Tim Peters887c0802006-01-20 23:40:56 +0000125
Georg Brandl23929f22006-01-20 21:03:35 +0000126 args = ['%s']
Tim Peters887c0802006-01-20 23:40:56 +0000127
Georg Brandle8f24432005-10-03 14:16:44 +0000128 def __init__(self, name=""):
129 self.name = name
Georg Brandlb9801132005-10-08 20:47:38 +0000130 self.basename = name
Tim Peters536cf992005-12-25 23:18:31 +0000131
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000132 def open(self, url, new=0, autoraise=True):
Neal Norwitz196f7332005-10-04 03:17:49 +0000133 raise NotImplementedError
134
Georg Brandle8f24432005-10-03 14:16:44 +0000135 def open_new(self, url):
136 return self.open(url, 1)
137
138 def open_new_tab(self, url):
139 return self.open(url, 2)
Fred Drake3f8f1642001-07-19 03:46:26 +0000140
141
Georg Brandle8f24432005-10-03 14:16:44 +0000142class GenericBrowser(BaseBrowser):
143 """Class for all browsers started with a command
144 and without remote functionality."""
145
Georg Brandl23929f22006-01-20 21:03:35 +0000146 def __init__(self, name):
Guido van Rossum3172c5d2007-10-16 18:12:55 +0000147 if isinstance(name, str):
Georg Brandl23929f22006-01-20 21:03:35 +0000148 self.name = name
Guido van Rossum992d4a32007-07-11 13:09:30 +0000149 self.args = ["%s"]
Georg Brandl23929f22006-01-20 21:03:35 +0000150 else:
151 # name should be a list with arguments
152 self.name = name[0]
153 self.args = name[1:]
Georg Brandlb9801132005-10-08 20:47:38 +0000154 self.basename = os.path.basename(self.name)
Fred Drake3f8f1642001-07-19 03:46:26 +0000155
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000156 def open(self, url, new=0, autoraise=True):
Tim Peters887c0802006-01-20 23:40:56 +0000157 cmdline = [self.name] + [arg.replace("%s", url)
Georg Brandl23929f22006-01-20 21:03:35 +0000158 for arg in self.args]
159 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000160 if sys.platform[:3] == 'win':
161 p = subprocess.Popen(cmdline)
162 else:
163 p = subprocess.Popen(cmdline, close_fds=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000164 return not p.wait()
165 except OSError:
166 return False
167
168
169class BackgroundBrowser(GenericBrowser):
170 """Class for all browsers which are to be started in the
171 background."""
172
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000173 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000174 cmdline = [self.name] + [arg.replace("%s", url)
175 for arg in self.args]
Georg Brandl23929f22006-01-20 21:03:35 +0000176 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000177 if sys.platform[:3] == 'win':
178 p = subprocess.Popen(cmdline)
179 else:
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700180 p = subprocess.Popen(cmdline, close_fds=True,
181 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000182 return (p.poll() is None)
183 except OSError:
184 return False
Fred Drake3f8f1642001-07-19 03:46:26 +0000185
186
Georg Brandle8f24432005-10-03 14:16:44 +0000187class UnixBrowser(BaseBrowser):
188 """Parent class for all Unix browsers with remote functionality."""
Fred Drake3f8f1642001-07-19 03:46:26 +0000189
Georg Brandle8f24432005-10-03 14:16:44 +0000190 raise_opts = None
R David Murray94dd7cb2012-09-03 12:30:12 -0400191 background = False
192 redirect_stdout = True
193 # In remote_args, %s will be replaced with the requested URL. %action will
194 # be replaced depending on the value of 'new' passed to open.
195 # remote_action is used for new=0 (open). If newwin is not None, it is
196 # used for new=1 (open_new). If newtab is not None, it is used for
197 # new=3 (open_new_tab). After both substitutions are made, any empty
198 # strings in the transformed remote_args list will be removed.
Georg Brandl23929f22006-01-20 21:03:35 +0000199 remote_args = ['%action', '%s']
Georg Brandle8f24432005-10-03 14:16:44 +0000200 remote_action = None
201 remote_action_newwin = None
202 remote_action_newtab = None
Georg Brandle8f24432005-10-03 14:16:44 +0000203
Georg Brandl23929f22006-01-20 21:03:35 +0000204 def _invoke(self, args, remote, autoraise):
205 raise_opt = []
206 if remote and self.raise_opts:
207 # use autoraise argument only for remote invocation
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000208 autoraise = int(autoraise)
Georg Brandl23929f22006-01-20 21:03:35 +0000209 opt = self.raise_opts[autoraise]
210 if opt: raise_opt = [opt]
211
212 cmdline = [self.name] + raise_opt + args
Tim Peters887c0802006-01-20 23:40:56 +0000213
Georg Brandl23929f22006-01-20 21:03:35 +0000214 if remote or self.background:
R David Murray02ca1442012-09-03 12:44:29 -0400215 inout = subprocess.DEVNULL
Georg Brandl23929f22006-01-20 21:03:35 +0000216 else:
217 # for TTY browsers, we need stdin/out
218 inout = None
Georg Brandl23929f22006-01-20 21:03:35 +0000219 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
220 stdout=(self.redirect_stdout and inout or None),
Gregory P. Smith8f7724f2011-03-15 15:24:43 -0400221 stderr=inout, start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000222 if remote:
Jesus Ceac9aa3212012-08-01 03:57:52 +0200223 # wait at most five seconds. If the subprocess is not finished, the
Georg Brandl23929f22006-01-20 21:03:35 +0000224 # remote invocation has (hopefully) started a new instance.
Jesus Ceac9aa3212012-08-01 03:57:52 +0200225 try:
226 rc = p.wait(5)
227 # if remote call failed, open() will try direct invocation
228 return not rc
229 except subprocess.TimeoutExpired:
230 return True
Georg Brandl23929f22006-01-20 21:03:35 +0000231 elif self.background:
232 if p.poll() is None:
233 return True
234 else:
235 return False
236 else:
237 return not p.wait()
Fred Drake3f8f1642001-07-19 03:46:26 +0000238
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000239 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000240 if new == 0:
241 action = self.remote_action
242 elif new == 1:
243 action = self.remote_action_newwin
244 elif new == 2:
245 if self.remote_action_newtab is None:
246 action = self.remote_action_newwin
247 else:
248 action = self.remote_action_newtab
Fred Drake3f8f1642001-07-19 03:46:26 +0000249 else:
Georg Brandl23929f22006-01-20 21:03:35 +0000250 raise Error("Bad 'new' parameter to open(); " +
251 "expected 0, 1, or 2, got %s" % new)
Tim Peters887c0802006-01-20 23:40:56 +0000252
Georg Brandl23929f22006-01-20 21:03:35 +0000253 args = [arg.replace("%s", url).replace("%action", action)
254 for arg in self.remote_args]
R David Murray94dd7cb2012-09-03 12:30:12 -0400255 args = [arg for arg in args if arg]
Georg Brandl23929f22006-01-20 21:03:35 +0000256 success = self._invoke(args, True, autoraise)
257 if not success:
258 # remote invocation failed, try straight way
259 args = [arg.replace("%s", url) for arg in self.args]
260 return self._invoke(args, False, False)
261 else:
262 return True
Fred Drake3f8f1642001-07-19 03:46:26 +0000263
264
Georg Brandle8f24432005-10-03 14:16:44 +0000265class Mozilla(UnixBrowser):
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200266 """Launcher class for Mozilla browsers."""
267
268 remote_args = ['%action', '%s']
269 remote_action = ""
270 remote_action_newwin = "-new-window"
271 remote_action_newtab = "-new-tab"
272 background = True
273
274
275class Netscape(UnixBrowser):
276 """Launcher class for Netscape browser."""
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000277
Georg Brandl23929f22006-01-20 21:03:35 +0000278 raise_opts = ["-noraise", "-raise"]
Georg Brandl23929f22006-01-20 21:03:35 +0000279 remote_args = ['-remote', 'openURL(%s%action)']
280 remote_action = ""
281 remote_action_newwin = ",new-window"
282 remote_action_newtab = ",new-tab"
Georg Brandl23929f22006-01-20 21:03:35 +0000283 background = True
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000284
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000285
Georg Brandle8f24432005-10-03 14:16:44 +0000286class Galeon(UnixBrowser):
287 """Launcher class for Galeon/Epiphany browsers."""
288
Georg Brandl23929f22006-01-20 21:03:35 +0000289 raise_opts = ["-noraise", ""]
290 remote_args = ['%action', '%s']
291 remote_action = "-n"
292 remote_action_newwin = "-w"
Georg Brandl23929f22006-01-20 21:03:35 +0000293 background = True
Fred Drake3f8f1642001-07-19 03:46:26 +0000294
295
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800296class Chrome(UnixBrowser):
297 "Launcher class for Google Chrome browser."
298
299 remote_args = ['%action', '%s']
300 remote_action = ""
301 remote_action_newwin = "--new-window"
302 remote_action_newtab = ""
303 background = True
304
305Chromium = Chrome
306
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
R David Murray02ca1442012-09-03 12:44:29 -0400347 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000348
Georg Brandl23929f22006-01-20 21:03:35 +0000349 try:
350 p = subprocess.Popen(["kfmclient", action, url],
351 close_fds=True, stdin=devnull,
352 stdout=devnull, stderr=devnull)
353 except OSError:
354 # fall through to next variant
355 pass
356 else:
357 p.wait()
358 # kfmclient's return code unfortunately has no meaning as it seems
359 return True
360
361 try:
362 p = subprocess.Popen(["konqueror", "--silent", url],
363 close_fds=True, stdin=devnull,
364 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700365 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000366 except OSError:
367 # fall through to next variant
368 pass
369 else:
370 if p.poll() is None:
371 # Should be running now.
372 return True
Tim Peters887c0802006-01-20 23:40:56 +0000373
Georg Brandl23929f22006-01-20 21:03:35 +0000374 try:
375 p = subprocess.Popen(["kfm", "-d", url],
376 close_fds=True, stdin=devnull,
377 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700378 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000379 except OSError:
380 return False
381 else:
382 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000383
384
385class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000386 # There should be a way to maintain a connection to Grail, but the
387 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000388 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000389 def _find_grail_rc(self):
390 import glob
391 import pwd
392 import socket
393 import tempfile
394 tempdir = os.path.join(tempfile.gettempdir(),
395 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000396 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000397 filename = os.path.join(tempdir, user + "-*")
398 maybes = glob.glob(filename)
399 if not maybes:
400 return None
401 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
402 for fn in maybes:
403 # need to PING each one until we find one that's live
404 try:
405 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200406 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000407 # no good; attempt to clean it out, but don't fail:
408 try:
409 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200410 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000411 pass
412 else:
413 return s
414
415 def _remote(self, action):
416 s = self._find_grail_rc()
417 if not s:
418 return 0
419 s.send(action)
420 s.close()
421 return 1
422
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000423 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000424 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000425 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000426 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000427 ok = self._remote("LOAD " + url)
428 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000429
Fred Drakec70b4482000-07-09 16:45:56 +0000430
Tim Peters658cba62001-02-09 20:06:00 +0000431#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000432# Platform support for Unix
433#
Fred Drakec70b4482000-07-09 16:45:56 +0000434
Georg Brandle8f24432005-10-03 14:16:44 +0000435# These are the right tests because all these Unix browsers require either
436# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000437
Neal Norwitz196f7332005-10-04 03:17:49 +0000438def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000439
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200440 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200441 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200442 register("xdg-open", None, BackgroundBrowser("xdg-open"))
443
444 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200445 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200446 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
447
Guido van Rossumd8faa362007-04-27 19:54:29 +0000448 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200449 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000450 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000451
Guido van Rossumd8faa362007-04-27 19:54:29 +0000452 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200453 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000454 register("kfmclient", Konqueror, Konqueror("kfmclient"))
455
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100456 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100457 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
458
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200459 # The Mozilla browsers
460 for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200461 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000462 register(browser, None, Mozilla(browser))
463
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200464 # The Netscape and old Mozilla browsers
465 for browser in ("mozilla-firefox",
466 "mozilla-firebird", "firebird",
467 "mozilla", "netscape"):
468 if shutil.which(browser):
469 register(browser, None, Netscape(browser))
470
Georg Brandle8f24432005-10-03 14:16:44 +0000471 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200472 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000473 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200474 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000475 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000476
Georg Brandle8f24432005-10-03 14:16:44 +0000477 # Gnome's Galeon and Epiphany
478 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200479 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000480 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000481
Georg Brandle8f24432005-10-03 14:16:44 +0000482 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200483 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000484 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000485
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800486 # Google Chrome/Chromium browsers
487 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200488 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800489 register(browser, None, Chrome(browser))
490
Georg Brandle8f24432005-10-03 14:16:44 +0000491 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200492 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000493 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000494
Georg Brandle8f24432005-10-03 14:16:44 +0000495 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200496 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000497 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000498
Georg Brandle8f24432005-10-03 14:16:44 +0000499 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200500 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000501 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000502
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200503def register_standard_browsers():
504 global _tryorder
505 _tryorder = []
506
507 if sys.platform == 'darwin':
508 register("MacOSX", None, MacOSXOSAScript('default'))
509 register("chrome", None, MacOSXOSAScript('chrome'))
510 register("firefox", None, MacOSXOSAScript('firefox'))
511 register("safari", None, MacOSXOSAScript('safari'))
512 # OS X can use below Unix support (but we prefer using the OS X
513 # specific stuff)
514
515 if sys.platform[:3] == "win":
516 # First try to use the default Windows browser
517 register("windows-default", WindowsDefault)
518
519 # Detect some common Windows browsers, fallback to IE
520 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
521 "Internet Explorer\\IEXPLORE.EXE")
522 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
523 "netscape", "opera", iexplore):
524 if shutil.which(browser):
525 register(browser, None, BackgroundBrowser(browser))
David Steelee3ce6952017-02-24 23:47:38 -0500526 else:
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200527 # Prefer X browsers if present
528 if os.environ.get("DISPLAY"):
529 try:
530 cmd = "xdg-settings get default-web-browser".split()
531 raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
532 result = raw_result.decode().strip()
533 except (FileNotFoundError, subprocess.CalledProcessError):
534 pass
535 else:
536 global _os_preferred_browser
537 _os_preferred_browser = result
David Steelee3ce6952017-02-24 23:47:38 -0500538
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200539 register_X_browsers()
Neal Norwitz196f7332005-10-04 03:17:49 +0000540
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200541 # Also try console browsers
542 if os.environ.get("TERM"):
543 if shutil.which("www-browser"):
544 register("www-browser", None, GenericBrowser("www-browser"))
545 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
546 if shutil.which("links"):
547 register("links", None, GenericBrowser("links"))
548 if shutil.which("elinks"):
549 register("elinks", None, Elinks("elinks"))
550 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
551 if shutil.which("lynx"):
552 register("lynx", None, GenericBrowser("lynx"))
553 # The w3m browser <http://w3m.sourceforge.net/>
554 if shutil.which("w3m"):
555 register("w3m", None, GenericBrowser("w3m"))
556
557 # OK, now that we know what the default preference orders for each
558 # platform are, allow user to override them with the BROWSER variable.
559 if "BROWSER" in os.environ:
560 userchoices = os.environ["BROWSER"].split(os.pathsep)
561 userchoices.reverse()
562
563 # Treat choices in same way as if passed into get() but do register
564 # and prepend to _tryorder
565 for cmdline in userchoices:
566 if cmdline != '':
567 cmd = _synthesize(cmdline, -1)
568 if cmd[1] is None:
569 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
570
571 # what to do if _tryorder is now empty?
572
Fred Drake3f8f1642001-07-19 03:46:26 +0000573
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000574#
575# Platform support for Windows
576#
Fred Drakec70b4482000-07-09 16:45:56 +0000577
578if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000579 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000580 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000581 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700582 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200583 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000584 # [Error 22] No application is associated with the specified
585 # file for this operation: '<URL>'
586 return False
587 else:
588 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000589
Fred Drakec70b4482000-07-09 16:45:56 +0000590#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000591# Platform support for MacOS
592#
Fred Drakec70b4482000-07-09 16:45:56 +0000593
Georg Brandle8f24432005-10-03 14:16:44 +0000594if sys.platform == 'darwin':
595 # Adapted from patch submitted to SourceForge by Steven J. Burr
596 class MacOSX(BaseBrowser):
597 """Launcher class for Aqua browsers on Mac OS X
598
599 Optionally specify a browser name on instantiation. Note that this
600 will not work for Aqua browsers if the user has moved the application
601 package after installation.
602
603 If no browser is specified, the default browser, as specified in the
604 Internet System Preferences panel, will be used.
605 """
606 def __init__(self, name):
607 self.name = name
608
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000609 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000610 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000611 # hack for local urls
612 if not ':' in url:
613 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000614
Georg Brandle8f24432005-10-03 14:16:44 +0000615 # new must be 0 or 1
616 new = int(bool(new))
617 if self.name == "default":
618 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000619 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000620 else:
621 # User called get and chose a browser
622 if self.name == "OmniWeb":
623 toWindow = ""
624 else:
625 # Include toWindow parameter of OpenURL command for browsers
626 # that support it. 0 == new window; -1 == existing
627 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000628 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000629 script = '''tell application "%s"
630 activate
631 %s %s
632 end tell''' % (self.name, cmd, toWindow)
633 # Open pipe to AppleScript through osascript command
634 osapipe = os.popen("osascript", "w")
635 if osapipe is None:
636 return False
637 # Write script to osascript's stdin
638 osapipe.write(script)
639 rc = osapipe.close()
640 return not rc
641
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000642 class MacOSXOSAScript(BaseBrowser):
643 def __init__(self, name):
644 self._name = name
645
646 def open(self, url, new=0, autoraise=True):
647 if self._name == 'default':
648 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
649 else:
650 script = '''
651 tell application "%s"
652 activate
653 open location "%s"
654 end
655 '''%(self._name, url.replace('"', '%22'))
656
657 osapipe = os.popen("osascript", "w")
658 if osapipe is None:
659 return False
660
661 osapipe.write(script)
662 rc = osapipe.close()
663 return not rc
664
665
Georg Brandle8f24432005-10-03 14:16:44 +0000666def main():
667 import getopt
668 usage = """Usage: %s [-n | -t] url
669 -n: open new window
670 -t: open new tab""" % sys.argv[0]
671 try:
672 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000673 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000674 print(msg, file=sys.stderr)
675 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000676 sys.exit(1)
677 new_win = 0
678 for o, a in opts:
679 if o == '-n': new_win = 1
680 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000681 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000682 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000683 sys.exit(1)
684
685 url = args[0]
686 open(url, new_win)
687
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000688 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000689
Georg Brandle8f24432005-10-03 14:16:44 +0000690if __name__ == "__main__":
691 main()