blob: d717193d05c21fee7e3c5d52c9f2d98ddf4ec69d [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
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400311 remote_args = ['%action', '%s']
Georg Brandl23929f22006-01-20 21:03:35 +0000312 remote_action = ""
Bumsik Kim3cf1f152018-07-03 07:30:06 -0400313 remote_action_newwin = "--new-window"
314 remote_action_newtab = ""
Georg Brandl23929f22006-01-20 21:03:35 +0000315 background = True
Georg Brandle8f24432005-10-03 14:16:44 +0000316
317
318class Elinks(UnixBrowser):
319 "Launcher class for Elinks browsers."
320
Georg Brandl23929f22006-01-20 21:03:35 +0000321 remote_args = ['-remote', 'openURL(%s%action)']
322 remote_action = ""
323 remote_action_newwin = ",new-window"
324 remote_action_newtab = ",new-tab"
325 background = False
Georg Brandle8f24432005-10-03 14:16:44 +0000326
Georg Brandl23929f22006-01-20 21:03:35 +0000327 # elinks doesn't like its stdout to be redirected -
328 # it uses redirected stdout as a signal to do -dump
329 redirect_stdout = False
330
331
332class Konqueror(BaseBrowser):
333 """Controller for the KDE File Manager (kfm, or Konqueror).
334
335 See the output of ``kfmclient --commands``
336 for more information on the Konqueror remote-control interface.
337 """
338
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000339 def open(self, url, new=0, autoraise=True):
Georg Brandl23929f22006-01-20 21:03:35 +0000340 # XXX Currently I know no way to prevent KFM from opening a new win.
341 if new == 2:
342 action = "newTab"
343 else:
344 action = "openURL"
Tim Peters887c0802006-01-20 23:40:56 +0000345
R David Murray02ca1442012-09-03 12:44:29 -0400346 devnull = subprocess.DEVNULL
Tim Peters887c0802006-01-20 23:40:56 +0000347
Georg Brandl23929f22006-01-20 21:03:35 +0000348 try:
349 p = subprocess.Popen(["kfmclient", action, url],
350 close_fds=True, stdin=devnull,
351 stdout=devnull, stderr=devnull)
352 except OSError:
353 # fall through to next variant
354 pass
355 else:
356 p.wait()
357 # kfmclient's return code unfortunately has no meaning as it seems
358 return True
359
360 try:
361 p = subprocess.Popen(["konqueror", "--silent", url],
362 close_fds=True, stdin=devnull,
363 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700364 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000365 except OSError:
366 # fall through to next variant
367 pass
368 else:
369 if p.poll() is None:
370 # Should be running now.
371 return True
Tim Peters887c0802006-01-20 23:40:56 +0000372
Georg Brandl23929f22006-01-20 21:03:35 +0000373 try:
374 p = subprocess.Popen(["kfm", "-d", url],
375 close_fds=True, stdin=devnull,
376 stdout=devnull, stderr=devnull,
Gregory P. Smithfeac3982014-08-27 09:34:38 -0700377 start_new_session=True)
Georg Brandl23929f22006-01-20 21:03:35 +0000378 except OSError:
379 return False
380 else:
381 return (p.poll() is None)
Georg Brandle8f24432005-10-03 14:16:44 +0000382
383
384class Grail(BaseBrowser):
Fred Drake3f8f1642001-07-19 03:46:26 +0000385 # There should be a way to maintain a connection to Grail, but the
386 # Grail remote control protocol doesn't really allow that at this
Georg Brandl23929f22006-01-20 21:03:35 +0000387 # point. It probably never will!
Fred Drake3f8f1642001-07-19 03:46:26 +0000388 def _find_grail_rc(self):
389 import glob
390 import pwd
391 import socket
392 import tempfile
393 tempdir = os.path.join(tempfile.gettempdir(),
394 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000395 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000396 filename = os.path.join(tempdir, user + "-*")
397 maybes = glob.glob(filename)
398 if not maybes:
399 return None
400 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
401 for fn in maybes:
402 # need to PING each one until we find one that's live
403 try:
404 s.connect(fn)
Andrew Svetlov0832af62012-12-18 23:10:48 +0200405 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000406 # no good; attempt to clean it out, but don't fail:
407 try:
408 os.unlink(fn)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200409 except OSError:
Fred Drake3f8f1642001-07-19 03:46:26 +0000410 pass
411 else:
412 return s
413
414 def _remote(self, action):
415 s = self._find_grail_rc()
416 if not s:
417 return 0
418 s.send(action)
419 s.close()
420 return 1
421
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000422 def open(self, url, new=0, autoraise=True):
Fred Drake3f8f1642001-07-19 03:46:26 +0000423 if new:
Georg Brandle8f24432005-10-03 14:16:44 +0000424 ok = self._remote("LOADNEW " + url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000425 else:
Georg Brandle8f24432005-10-03 14:16:44 +0000426 ok = self._remote("LOAD " + url)
427 return ok
Fred Drake3f8f1642001-07-19 03:46:26 +0000428
Fred Drakec70b4482000-07-09 16:45:56 +0000429
Tim Peters658cba62001-02-09 20:06:00 +0000430#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000431# Platform support for Unix
432#
Fred Drakec70b4482000-07-09 16:45:56 +0000433
Georg Brandle8f24432005-10-03 14:16:44 +0000434# These are the right tests because all these Unix browsers require either
435# a console terminal or an X display to run.
Fred Drakec70b4482000-07-09 16:45:56 +0000436
Neal Norwitz196f7332005-10-04 03:17:49 +0000437def register_X_browsers():
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000438
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200439 # use xdg-open if around
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200440 if shutil.which("xdg-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200441 register("xdg-open", None, BackgroundBrowser("xdg-open"))
442
443 # The default GNOME3 browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200444 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gvfs-open"):
Matthias Kloseda80b1e2012-04-04 14:19:04 +0200445 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
446
Guido van Rossumd8faa362007-04-27 19:54:29 +0000447 # The default GNOME browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200448 if "GNOME_DESKTOP_SESSION_ID" in os.environ and shutil.which("gnome-open"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000449 register("gnome-open", None, BackgroundBrowser("gnome-open"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000450
Guido van Rossumd8faa362007-04-27 19:54:29 +0000451 # The default KDE browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200452 if "KDE_FULL_SESSION" in os.environ and shutil.which("kfmclient"):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000453 register("kfmclient", Konqueror, Konqueror("kfmclient"))
454
doko@ubuntu.comf85aca82013-03-24 18:50:23 +0100455 if shutil.which("x-www-browser"):
doko@ubuntu.com945c3bb2013-03-24 18:46:49 +0100456 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
457
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200458 # The Mozilla browsers
459 for browser in ("firefox", "iceweasel", "iceape", "seamonkey"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200460 if shutil.which(browser):
Georg Brandl4a5a9182005-11-22 19:18:01 +0000461 register(browser, None, Mozilla(browser))
462
Serhiy Storchakac9b750d2016-10-30 19:16:33 +0200463 # The Netscape and old Mozilla browsers
464 for browser in ("mozilla-firefox",
465 "mozilla-firebird", "firebird",
466 "mozilla", "netscape"):
467 if shutil.which(browser):
468 register(browser, None, Netscape(browser))
469
Georg Brandle8f24432005-10-03 14:16:44 +0000470 # Konqueror/kfm, the KDE browser.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200471 if shutil.which("kfm"):
Georg Brandlb9801132005-10-08 20:47:38 +0000472 register("kfm", Konqueror, Konqueror("kfm"))
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200473 elif shutil.which("konqueror"):
Georg Brandlb9801132005-10-08 20:47:38 +0000474 register("konqueror", Konqueror, Konqueror("konqueror"))
Neal Norwitz8dd28eb2002-10-10 22:49:29 +0000475
Georg Brandle8f24432005-10-03 14:16:44 +0000476 # Gnome's Galeon and Epiphany
477 for browser in ("galeon", "epiphany"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200478 if shutil.which(browser):
Georg Brandle8f24432005-10-03 14:16:44 +0000479 register(browser, None, Galeon(browser))
Gustavo Niemeyer1456fde2002-11-25 17:25:04 +0000480
Georg Brandle8f24432005-10-03 14:16:44 +0000481 # Skipstone, another Gtk/Mozilla based browser
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200482 if shutil.which("skipstone"):
Georg Brandl23929f22006-01-20 21:03:35 +0000483 register("skipstone", None, BackgroundBrowser("skipstone"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000484
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800485 # Google Chrome/Chromium browsers
486 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200487 if shutil.which(browser):
Senthil Kumaranea6b4182011-12-21 22:20:32 +0800488 register(browser, None, Chrome(browser))
489
Georg Brandle8f24432005-10-03 14:16:44 +0000490 # Opera, quite popular
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200491 if shutil.which("opera"):
Georg Brandle8f24432005-10-03 14:16:44 +0000492 register("opera", None, Opera("opera"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000493
Georg Brandle8f24432005-10-03 14:16:44 +0000494 # Next, Mosaic -- old but still in use.
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200495 if shutil.which("mosaic"):
Georg Brandl23929f22006-01-20 21:03:35 +0000496 register("mosaic", None, BackgroundBrowser("mosaic"))
Fred Drake3f8f1642001-07-19 03:46:26 +0000497
Georg Brandle8f24432005-10-03 14:16:44 +0000498 # Grail, the Python browser. Does anybody still use it?
Serhiy Storchaka540dcba2013-02-13 12:19:40 +0200499 if shutil.which("grail"):
Georg Brandle8f24432005-10-03 14:16:44 +0000500 register("grail", Grail, None)
Fred Drake3f8f1642001-07-19 03:46:26 +0000501
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200502def register_standard_browsers():
503 global _tryorder
504 _tryorder = []
505
506 if sys.platform == 'darwin':
507 register("MacOSX", None, MacOSXOSAScript('default'))
508 register("chrome", None, MacOSXOSAScript('chrome'))
509 register("firefox", None, MacOSXOSAScript('firefox'))
510 register("safari", None, MacOSXOSAScript('safari'))
511 # OS X can use below Unix support (but we prefer using the OS X
512 # specific stuff)
513
514 if sys.platform[:3] == "win":
515 # First try to use the default Windows browser
516 register("windows-default", WindowsDefault)
517
518 # Detect some common Windows browsers, fallback to IE
519 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
520 "Internet Explorer\\IEXPLORE.EXE")
521 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
522 "netscape", "opera", iexplore):
523 if shutil.which(browser):
524 register(browser, None, BackgroundBrowser(browser))
David Steelee3ce6952017-02-24 23:47:38 -0500525 else:
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200526 # Prefer X browsers if present
527 if os.environ.get("DISPLAY"):
528 try:
529 cmd = "xdg-settings get default-web-browser".split()
530 raw_result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
531 result = raw_result.decode().strip()
532 except (FileNotFoundError, subprocess.CalledProcessError):
533 pass
534 else:
535 global _os_preferred_browser
536 _os_preferred_browser = result
David Steelee3ce6952017-02-24 23:47:38 -0500537
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200538 register_X_browsers()
Neal Norwitz196f7332005-10-04 03:17:49 +0000539
Serhiy Storchakaa7cba272017-03-08 17:15:54 +0200540 # Also try console browsers
541 if os.environ.get("TERM"):
542 if shutil.which("www-browser"):
543 register("www-browser", None, GenericBrowser("www-browser"))
544 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
545 if shutil.which("links"):
546 register("links", None, GenericBrowser("links"))
547 if shutil.which("elinks"):
548 register("elinks", None, Elinks("elinks"))
549 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
550 if shutil.which("lynx"):
551 register("lynx", None, GenericBrowser("lynx"))
552 # The w3m browser <http://w3m.sourceforge.net/>
553 if shutil.which("w3m"):
554 register("w3m", None, GenericBrowser("w3m"))
555
556 # OK, now that we know what the default preference orders for each
557 # platform are, allow user to override them with the BROWSER variable.
558 if "BROWSER" in os.environ:
559 userchoices = os.environ["BROWSER"].split(os.pathsep)
560 userchoices.reverse()
561
562 # Treat choices in same way as if passed into get() but do register
563 # and prepend to _tryorder
564 for cmdline in userchoices:
565 if cmdline != '':
566 cmd = _synthesize(cmdline, -1)
567 if cmd[1] is None:
568 register(cmdline, None, GenericBrowser(cmdline), preferred=True)
569
570 # what to do if _tryorder is now empty?
571
Fred Drake3f8f1642001-07-19 03:46:26 +0000572
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000573#
574# Platform support for Windows
575#
Fred Drakec70b4482000-07-09 16:45:56 +0000576
577if sys.platform[:3] == "win":
Georg Brandle8f24432005-10-03 14:16:44 +0000578 class WindowsDefault(BaseBrowser):
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000579 def open(self, url, new=0, autoraise=True):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000580 try:
Steve Dower2ebd8f52015-09-05 11:57:47 -0700581 os.startfile(url)
Andrew Svetlov2606a6f2012-12-19 14:33:35 +0200582 except OSError:
Guido van Rossumd8faa362007-04-27 19:54:29 +0000583 # [Error 22] No application is associated with the specified
584 # file for this operation: '<URL>'
585 return False
586 else:
587 return True
Georg Brandle8f24432005-10-03 14:16:44 +0000588
Fred Drakec70b4482000-07-09 16:45:56 +0000589#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000590# Platform support for MacOS
591#
Fred Drakec70b4482000-07-09 16:45:56 +0000592
Georg Brandle8f24432005-10-03 14:16:44 +0000593if sys.platform == 'darwin':
594 # Adapted from patch submitted to SourceForge by Steven J. Burr
595 class MacOSX(BaseBrowser):
596 """Launcher class for Aqua browsers on Mac OS X
597
598 Optionally specify a browser name on instantiation. Note that this
599 will not work for Aqua browsers if the user has moved the application
600 package after installation.
601
602 If no browser is specified, the default browser, as specified in the
603 Internet System Preferences panel, will be used.
604 """
605 def __init__(self, name):
606 self.name = name
607
Alexandre Vassalottie223eb82009-07-29 20:12:15 +0000608 def open(self, url, new=0, autoraise=True):
Georg Brandle8f24432005-10-03 14:16:44 +0000609 assert "'" not in url
Georg Brandl23929f22006-01-20 21:03:35 +0000610 # hack for local urls
611 if not ':' in url:
612 url = 'file:'+url
Tim Peters887c0802006-01-20 23:40:56 +0000613
Georg Brandle8f24432005-10-03 14:16:44 +0000614 # new must be 0 or 1
615 new = int(bool(new))
616 if self.name == "default":
617 # User called open, open_new or get without a browser parameter
Georg Brandl1cb179e2005-11-09 21:42:48 +0000618 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
Georg Brandle8f24432005-10-03 14:16:44 +0000619 else:
620 # User called get and chose a browser
621 if self.name == "OmniWeb":
622 toWindow = ""
623 else:
624 # Include toWindow parameter of OpenURL command for browsers
625 # that support it. 0 == new window; -1 == existing
626 toWindow = "toWindow %d" % (new - 1)
Georg Brandl1cb179e2005-11-09 21:42:48 +0000627 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
Georg Brandle8f24432005-10-03 14:16:44 +0000628 script = '''tell application "%s"
629 activate
630 %s %s
631 end tell''' % (self.name, cmd, toWindow)
632 # Open pipe to AppleScript through osascript command
633 osapipe = os.popen("osascript", "w")
634 if osapipe is None:
635 return False
636 # Write script to osascript's stdin
637 osapipe.write(script)
638 rc = osapipe.close()
639 return not rc
640
Ronald Oussoren4d39f6e2010-05-02 09:54:35 +0000641 class MacOSXOSAScript(BaseBrowser):
642 def __init__(self, name):
643 self._name = name
644
645 def open(self, url, new=0, autoraise=True):
646 if self._name == 'default':
647 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
648 else:
649 script = '''
650 tell application "%s"
651 activate
652 open location "%s"
653 end
654 '''%(self._name, url.replace('"', '%22'))
655
656 osapipe = os.popen("osascript", "w")
657 if osapipe is None:
658 return False
659
660 osapipe.write(script)
661 rc = osapipe.close()
662 return not rc
663
664
Georg Brandle8f24432005-10-03 14:16:44 +0000665def main():
666 import getopt
667 usage = """Usage: %s [-n | -t] url
668 -n: open new window
669 -t: open new tab""" % sys.argv[0]
670 try:
671 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
Guido van Rossumb940e112007-01-10 16:19:56 +0000672 except getopt.error as msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000673 print(msg, file=sys.stderr)
674 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000675 sys.exit(1)
676 new_win = 0
677 for o, a in opts:
678 if o == '-n': new_win = 1
679 elif o == '-t': new_win = 2
Guido van Rossumb053cd82006-08-24 03:53:23 +0000680 if len(args) != 1:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000681 print(usage, file=sys.stderr)
Georg Brandle8f24432005-10-03 14:16:44 +0000682 sys.exit(1)
683
684 url = args[0]
685 open(url, new_win)
686
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000687 print("\a")
Georg Brandl23929f22006-01-20 21:03:35 +0000688
Georg Brandle8f24432005-10-03 14:16:44 +0000689if __name__ == "__main__":
690 main()