blob: ad195e0a3d0923df385110c6da07f449e8f7e5f7 [file] [log] [blame]
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +00001"""Interfaces for launching and remotely controlling Web browsers."""
Fred Drakec70b4482000-07-09 16:45:56 +00002
3import os
4import sys
5
Skip Montanaro40fc1602001-03-01 04:27:19 +00006__all__ = ["Error", "open", "get", "register"]
7
Fred Drakec70b4482000-07-09 16:45:56 +00008class Error(Exception):
9 pass
10
Tim Peters658cba62001-02-09 20:06:00 +000011_browsers = {} # Dictionary of available browser controllers
12_tryorder = [] # Preference order of available browsers
Fred Drakec70b4482000-07-09 16:45:56 +000013
14def register(name, klass, instance=None):
15 """Register a browser connector and, optionally, connection."""
16 _browsers[name.lower()] = [klass, instance]
17
Eric S. Raymondf7f18512001-01-23 13:16:32 +000018def get(using=None):
19 """Return a browser launcher instance appropriate for the environment."""
20 if using:
21 alternatives = [using]
22 else:
23 alternatives = _tryorder
24 for browser in alternatives:
25 if browser.find('%s') > -1:
26 # User gave us a command line, don't mess with it.
Eric S. Raymondf7eb4fa2001-03-31 01:50:52 +000027 return GenericBrowser(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000028 else:
Tim Peters658cba62001-02-09 20:06:00 +000029 # User gave us a browser name.
Fred Drakef4e5bd92001-04-12 22:07:27 +000030 try:
31 command = _browsers[browser.lower()]
32 except KeyError:
33 command = _synthesize(browser)
Eric S. Raymondf7f18512001-01-23 13:16:32 +000034 if command[1] is None:
35 return command[0]()
36 else:
37 return command[1]
38 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000039
40# Please note: the following definition hides a builtin function.
41
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +000042def open(url, new=0, autoraise=1):
43 get().open(url, new, autoraise)
Fred Drakec70b4482000-07-09 16:45:56 +000044
Tim Peters658cba62001-02-09 20:06:00 +000045def open_new(url): # Marked deprecated. May be removed in 2.1.
Eric S. Raymondf7f18512001-01-23 13:16:32 +000046 get().open(url, 1)
Fred Drakec70b4482000-07-09 16:45:56 +000047
Fred Drakef4e5bd92001-04-12 22:07:27 +000048
49def _synthesize(browser):
50 """Attempt to synthesize a controller base on existing controllers.
51
52 This is useful to create a controller when a user specifies a path to
53 an entry in the BROWSER environment variable -- we can copy a general
54 controller to operate using a specific installation of the desired
55 browser in this way.
56
57 If we can't create a controller in this way, or if there is no
58 executable for the requested browser, return [None, None].
59
60 """
61 if not os.path.exists(browser):
62 return [None, None]
63 name = os.path.basename(browser)
64 try:
65 command = _browsers[name.lower()]
66 except KeyError:
67 return [None, None]
68 # now attempt to clone to fit the new name:
69 controller = command[1]
70 if controller and name.lower() == controller.basename:
71 import copy
72 controller = copy.copy(controller)
73 controller.name = browser
74 controller.basename = os.path.basename(browser)
75 register(browser, None, controller)
76 return [None, controller]
77 ret
78
Eric S. Raymondf7f18512001-01-23 13:16:32 +000079#
80# Everything after this point initializes _browsers and _tryorder,
81# then disappears. Some class definitions and instances remain
82# live through these globals, but only the minimum set needed to
83# support the user's platform.
84#
Fred Drakec70b4482000-07-09 16:45:56 +000085
Tim Peters658cba62001-02-09 20:06:00 +000086#
Eric S. Raymondf7f18512001-01-23 13:16:32 +000087# Platform support for Unix
88#
Fred Drakec70b4482000-07-09 16:45:56 +000089
Eric S. Raymondf7f18512001-01-23 13:16:32 +000090# This is the right test because all these Unix browsers require either
91# a console terminal of an X display to run. Note that we cannot split
92# the TERM and DISPLAY cases, because we might be running Python from inside
93# an xterm.
94if os.environ.get("TERM") or os.environ.get("DISPLAY"):
95 PROCESS_CREATION_DELAY = 4
Eric S. Raymondf7f18512001-01-23 13:16:32 +000096 _tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m")
Fred Drakec70b4482000-07-09 16:45:56 +000097
Eric S. Raymondf7f18512001-01-23 13:16:32 +000098 def _iscommand(cmd):
99 """Return true if cmd can be found on the executable search path."""
100 path = os.environ.get("PATH")
101 if not path:
Fred Drakec70b4482000-07-09 16:45:56 +0000102 return 0
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000103 for d in path.split(os.pathsep):
104 exe = os.path.join(d, cmd)
105 if os.path.isfile(exe):
106 return 1
107 return 0
Fred Drakec70b4482000-07-09 16:45:56 +0000108
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000109 class GenericBrowser:
110 def __init__(self, cmd):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000111 self.name, self.args = cmd.split(None, 1)
112 self.basename = os.path.basename(self.name)
Fred Drakec70b4482000-07-09 16:45:56 +0000113
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000114 def open(self, url, new=0, autoraise=1):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000115 command = "%s %s" % (self.name, self.args)
116 os.system(command % url)
Fred Drakec70b4482000-07-09 16:45:56 +0000117
Tim Peters658cba62001-02-09 20:06:00 +0000118 def open_new(self, url): # Deprecated. May be removed in 2.1.
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000119 self.open(url)
Fred Drakec70b4482000-07-09 16:45:56 +0000120
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000121 # Easy cases first -- register console browsers if we have them.
122 if os.environ.get("TERM"):
123 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
124 if _iscommand("links"):
125 register("links", None, GenericBrowser("links %s"))
126 # The Lynx browser <http://lynx.browser.org/>
127 if _iscommand("lynx"):
128 register("lynx", None, GenericBrowser("lynx %s"))
129 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
130 if _iscommand("w3m"):
131 register("w3m", None, GenericBrowser("w3m %s"))
Fred Drakec70b4482000-07-09 16:45:56 +0000132
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +0000133 # X browsers have more in the way of options
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000134 if os.environ.get("DISPLAY"):
135 # First, the Netscape series
136 if _iscommand("netscape") or _iscommand("mozilla"):
137 class Netscape:
138 "Launcher class for Netscape browsers."
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000139 def __init__(self, name):
140 self.name = name
Fred Drakef4e5bd92001-04-12 22:07:27 +0000141 self.basename = os.path.basename(name)
Fred Drakec70b4482000-07-09 16:45:56 +0000142
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000143 def _remote(self, action, autoraise):
144 raise_opt = ("-noraise", "-raise")[autoraise]
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000145 cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name,
146 raise_opt,
147 action)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000148 rc = os.system(cmd)
149 if rc:
150 import time
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +0000151 os.system("%s &" % self.name)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000152 time.sleep(PROCESS_CREATION_DELAY)
153 rc = os.system(cmd)
154 return not rc
Fred Drakec70b4482000-07-09 16:45:56 +0000155
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000156 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000157 if new:
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000158 self._remote("openURL(%s, new-window)"%url, autoraise)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000159 else:
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000160 self._remote("openURL(%s)" % url, autoraise)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000161
162 # Deprecated. May be removed in 2.1.
163 def open_new(self, url):
164 self.open(url, 1)
165
166 if _iscommand("mozilla"):
167 register("mozilla", None, Netscape("mozilla"))
168 if _iscommand("netscape"):
169 register("netscape", None, Netscape("netscape"))
170
171 # Next, Mosaic -- old but still in use.
172 if _iscommand("mosaic"):
173 register("mosaic", None, GenericBrowser("mosaic %s >/dev/null &"))
174
175 # Konqueror/kfm, the KDE browser.
Fred Drakefc31f262001-03-26 15:06:15 +0000176 if _iscommand("kfm") or _iscommand("konqueror"):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000177 class Konqueror:
178 """Controller for the KDE File Manager (kfm, or Konqueror).
179
180 See http://developer.kde.org/documentation/other/kfmclient.html
181 for more information on the Konqueror remote-control interface.
182
183 """
Fred Drakef4e5bd92001-04-12 22:07:27 +0000184 def __init__(self):
185 if _iscommand("konqueror"):
186 self.name = self.basename = "konqueror"
187 else:
188 self.name = self.basename = "kfm"
189
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000190 def _remote(self, action):
191 cmd = "kfmclient %s >/dev/null 2>&1" % action
192 rc = os.system(cmd)
193 if rc:
194 import time
Fred Drakef4e5bd92001-04-12 22:07:27 +0000195 if self.basename == "konqueror":
196 os.system(self.name + " --silent &")
Fred Drakefc31f262001-03-26 15:06:15 +0000197 else:
Fred Drakef4e5bd92001-04-12 22:07:27 +0000198 os.system(self.name + " -d &")
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000199 time.sleep(PROCESS_CREATION_DELAY)
200 rc = os.system(cmd)
201 return not rc
202
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000203 def open(self, url, new=1, autoraise=1):
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000204 # XXX Currently I know no way to prevent KFM from
Tim Peters85ba6732001-02-28 08:26:44 +0000205 # opening a new win.
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000206 self._remote("openURL %s" % url)
207
208 # Deprecated. May be removed in 2.1.
209 open_new = open
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000210
Fred Drakef4e5bd92001-04-12 22:07:27 +0000211 register("kfm", Konqueror, Konqueror())
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000212
213 # Grail, the Python browser.
214 if _iscommand("grail"):
215 class Grail:
216 # There should be a way to maintain a connection to
217 # Grail, but the Grail remote control protocol doesn't
218 # really allow that at this point. It probably neverwill!
219 def _find_grail_rc(self):
220 import glob
221 import pwd
222 import socket
223 import tempfile
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000224 tempdir = os.path.join(tempfile.gettempdir(),
225 ".grail-unix")
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000226 user = pwd.getpwuid(_os.getuid())[0]
227 filename = os.path.join(tempdir, user + "-*")
228 maybes = glob.glob(filename)
229 if not maybes:
230 return None
231 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
232 for fn in maybes:
233 # need to PING each one until we find one that's live
234 try:
235 s.connect(fn)
236 except socket.error:
237 # no good; attempt to clean it out, but don't fail:
238 try:
239 os.unlink(fn)
240 except IOError:
241 pass
242 else:
243 return s
244
245 def _remote(self, action):
246 s = self._find_grail_rc()
247 if not s:
248 return 0
249 s.send(action)
250 s.close()
251 return 1
252
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000253 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000254 if new:
255 self._remote("LOADNEW " + url)
256 else:
257 self._remote("LOAD " + url)
258
259 # Deprecated. May be removed in 2.1.
260 def open_new(self, url):
261 self.open(url, 1)
262
263 register("grail", Grail, None)
264
265#
266# Platform support for Windows
267#
Fred Drakec70b4482000-07-09 16:45:56 +0000268
269if sys.platform[:3] == "win":
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000270 _tryorder = ("netscape", "windows-default")
271
272 class WindowsDefault:
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000273 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000274 os.startfile(url)
275
276 def open_new(self, url): # Deprecated. May be removed in 2.1.
277 self.open(url)
278
Fred Drakec70b4482000-07-09 16:45:56 +0000279 register("windows-default", WindowsDefault)
Fred Drakec70b4482000-07-09 16:45:56 +0000280
Fred Drakec70b4482000-07-09 16:45:56 +0000281#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000282# Platform support for MacOS
283#
Fred Drakec70b4482000-07-09 16:45:56 +0000284
285try:
286 import ic
287except ImportError:
288 pass
289else:
290 class InternetConfig:
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000291 def open(self, url, new=0, autoraise=1):
Guido van Rossum2595a832000-11-13 20:30:57 +0000292 ic.launchurl(url)
Fred Drakec70b4482000-07-09 16:45:56 +0000293
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000294 def open_new(self, url): # Deprecated. May be removed in 2.1.
Fred Drakec70b4482000-07-09 16:45:56 +0000295 self.open(url)
296
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000297 # internet-config is the only supported controller on MacOS,
298 # so don't mess with the default!
299 _tryorder = ("internet-config")
Fred Drakec70b4482000-07-09 16:45:56 +0000300 register("internet-config", InternetConfig)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000301
302# OK, now that we know what the default preference orders for each
303# platform are, allow user to override them with the BROWSER variable.
304#
305if os.environ.has_key("BROWSER"):
306 # It's the user's responsibility to register handlers for any unknown
307 # browser referenced by this value, before calling open().
308 _tryorder = os.environ["BROWSER"].split(":")
309else:
310 # Optimization: filter out alternatives that aren't available, so we can
311 # avoid has_key() tests at runtime. (This may also allow some unused
312 # classes and class-instance storage to be garbage-collected.)
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000313 _tryorder = filter(lambda x: _browsers.has_key(x.lower())
314 or x.find("%s") > -1, _tryorder)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000315
316# end