blob: ebafd80ba00699c24d475f36cccd5ee6c2c90900 [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.
27 return browser
28 else:
Tim Peters658cba62001-02-09 20:06:00 +000029 # User gave us a browser name.
Eric S. Raymondf7f18512001-01-23 13:16:32 +000030 command = _browsers[browser.lower()]
31 if command[1] is None:
32 return command[0]()
33 else:
34 return command[1]
35 raise Error("could not locate runnable browser")
Fred Drakec70b4482000-07-09 16:45:56 +000036
37# Please note: the following definition hides a builtin function.
38
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +000039def open(url, new=0, autoraise=1):
40 get().open(url, new, autoraise)
Fred Drakec70b4482000-07-09 16:45:56 +000041
Tim Peters658cba62001-02-09 20:06:00 +000042def open_new(url): # Marked deprecated. May be removed in 2.1.
Eric S. Raymondf7f18512001-01-23 13:16:32 +000043 get().open(url, 1)
Fred Drakec70b4482000-07-09 16:45:56 +000044
Eric S. Raymondf7f18512001-01-23 13:16:32 +000045#
46# Everything after this point initializes _browsers and _tryorder,
47# then disappears. Some class definitions and instances remain
48# live through these globals, but only the minimum set needed to
49# support the user's platform.
50#
Fred Drakec70b4482000-07-09 16:45:56 +000051
Tim Peters658cba62001-02-09 20:06:00 +000052#
Eric S. Raymondf7f18512001-01-23 13:16:32 +000053# Platform support for Unix
54#
Fred Drakec70b4482000-07-09 16:45:56 +000055
Eric S. Raymondf7f18512001-01-23 13:16:32 +000056# This is the right test because all these Unix browsers require either
57# a console terminal of an X display to run. Note that we cannot split
58# the TERM and DISPLAY cases, because we might be running Python from inside
59# an xterm.
60if os.environ.get("TERM") or os.environ.get("DISPLAY"):
61 PROCESS_CREATION_DELAY = 4
Eric S. Raymondf7f18512001-01-23 13:16:32 +000062 _tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m")
Fred Drakec70b4482000-07-09 16:45:56 +000063
Eric S. Raymondf7f18512001-01-23 13:16:32 +000064 def _iscommand(cmd):
65 """Return true if cmd can be found on the executable search path."""
66 path = os.environ.get("PATH")
67 if not path:
Fred Drakec70b4482000-07-09 16:45:56 +000068 return 0
Eric S. Raymondf7f18512001-01-23 13:16:32 +000069 for d in path.split(os.pathsep):
70 exe = os.path.join(d, cmd)
71 if os.path.isfile(exe):
72 return 1
73 return 0
Fred Drakec70b4482000-07-09 16:45:56 +000074
Eric S. Raymondf7f18512001-01-23 13:16:32 +000075 class GenericBrowser:
76 def __init__(self, cmd):
77 self.command = cmd
Fred Drakec70b4482000-07-09 16:45:56 +000078
Eric S. Raymondcfa40962001-01-23 15:49:34 +000079 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +000080 os.system(self.command % url)
Fred Drakec70b4482000-07-09 16:45:56 +000081
Tim Peters658cba62001-02-09 20:06:00 +000082 def open_new(self, url): # Deprecated. May be removed in 2.1.
Eric S. Raymondf7f18512001-01-23 13:16:32 +000083 self.open(url)
Fred Drakec70b4482000-07-09 16:45:56 +000084
Eric S. Raymondf7f18512001-01-23 13:16:32 +000085 # Easy cases first -- register console browsers if we have them.
86 if os.environ.get("TERM"):
87 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
88 if _iscommand("links"):
89 register("links", None, GenericBrowser("links %s"))
90 # The Lynx browser <http://lynx.browser.org/>
91 if _iscommand("lynx"):
92 register("lynx", None, GenericBrowser("lynx %s"))
93 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
94 if _iscommand("w3m"):
95 register("w3m", None, GenericBrowser("w3m %s"))
Fred Drakec70b4482000-07-09 16:45:56 +000096
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +000097 # X browsers have more in the way of options
Eric S. Raymondf7f18512001-01-23 13:16:32 +000098 if os.environ.get("DISPLAY"):
99 # First, the Netscape series
100 if _iscommand("netscape") or _iscommand("mozilla"):
101 class Netscape:
102 "Launcher class for Netscape browsers."
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000103 def __init__(self, name):
104 self.name = name
Fred Drakec70b4482000-07-09 16:45:56 +0000105
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000106 def _remote(self, action, autoraise):
107 raise_opt = ("-noraise", "-raise")[autoraise]
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000108 cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name,
109 raise_opt,
110 action)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000111 rc = os.system(cmd)
112 if rc:
113 import time
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +0000114 os.system("%s &" % self.name)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000115 time.sleep(PROCESS_CREATION_DELAY)
116 rc = os.system(cmd)
117 return not rc
Fred Drakec70b4482000-07-09 16:45:56 +0000118
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000119 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000120 if new:
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000121 self._remote("openURL(%s, new-window)"%url, autoraise)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000122 else:
Eric S. Raymondf79cb2d2001-01-23 13:49:44 +0000123 self._remote("openURL(%s)" % url, autoraise)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000124
125 # Deprecated. May be removed in 2.1.
126 def open_new(self, url):
127 self.open(url, 1)
128
129 if _iscommand("mozilla"):
130 register("mozilla", None, Netscape("mozilla"))
131 if _iscommand("netscape"):
132 register("netscape", None, Netscape("netscape"))
133
134 # Next, Mosaic -- old but still in use.
135 if _iscommand("mosaic"):
136 register("mosaic", None, GenericBrowser("mosaic %s >/dev/null &"))
137
138 # Konqueror/kfm, the KDE browser.
Fred Drakefc31f262001-03-26 15:06:15 +0000139 if _iscommand("kfm") or _iscommand("konqueror"):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000140 class Konqueror:
141 """Controller for the KDE File Manager (kfm, or Konqueror).
142
143 See http://developer.kde.org/documentation/other/kfmclient.html
144 for more information on the Konqueror remote-control interface.
145
146 """
147 def _remote(self, action):
148 cmd = "kfmclient %s >/dev/null 2>&1" % action
149 rc = os.system(cmd)
150 if rc:
151 import time
Fred Drakefc31f262001-03-26 15:06:15 +0000152 if _iscommand("konqueror"):
153 os.system("konqueror --silent &")
154 else:
155 os.system("kfm -d &")
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000156 time.sleep(PROCESS_CREATION_DELAY)
157 rc = os.system(cmd)
158 return not rc
159
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000160 def open(self, url, new=1, autoraise=1):
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000161 # XXX Currently I know no way to prevent KFM from
Tim Peters85ba6732001-02-28 08:26:44 +0000162 # opening a new win.
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000163 self._remote("openURL %s" % url)
164
165 # Deprecated. May be removed in 2.1.
166 open_new = open
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000167
168 register("kfm", Konqueror, None)
169
170 # Grail, the Python browser.
171 if _iscommand("grail"):
172 class Grail:
173 # There should be a way to maintain a connection to
174 # Grail, but the Grail remote control protocol doesn't
175 # really allow that at this point. It probably neverwill!
176 def _find_grail_rc(self):
177 import glob
178 import pwd
179 import socket
180 import tempfile
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000181 tempdir = os.path.join(tempfile.gettempdir(),
182 ".grail-unix")
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000183 user = pwd.getpwuid(_os.getuid())[0]
184 filename = os.path.join(tempdir, user + "-*")
185 maybes = glob.glob(filename)
186 if not maybes:
187 return None
188 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
189 for fn in maybes:
190 # need to PING each one until we find one that's live
191 try:
192 s.connect(fn)
193 except socket.error:
194 # no good; attempt to clean it out, but don't fail:
195 try:
196 os.unlink(fn)
197 except IOError:
198 pass
199 else:
200 return s
201
202 def _remote(self, action):
203 s = self._find_grail_rc()
204 if not s:
205 return 0
206 s.send(action)
207 s.close()
208 return 1
209
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000210 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000211 if new:
212 self._remote("LOADNEW " + url)
213 else:
214 self._remote("LOAD " + url)
215
216 # Deprecated. May be removed in 2.1.
217 def open_new(self, url):
218 self.open(url, 1)
219
220 register("grail", Grail, None)
221
222#
223# Platform support for Windows
224#
Fred Drakec70b4482000-07-09 16:45:56 +0000225
226if sys.platform[:3] == "win":
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000227 _tryorder = ("netscape", "windows-default")
228
229 class WindowsDefault:
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000230 def open(self, url, new=0, autoraise=1):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000231 os.startfile(url)
232
233 def open_new(self, url): # Deprecated. May be removed in 2.1.
234 self.open(url)
235
Fred Drakec70b4482000-07-09 16:45:56 +0000236 register("windows-default", WindowsDefault)
Fred Drakec70b4482000-07-09 16:45:56 +0000237
Fred Drakec70b4482000-07-09 16:45:56 +0000238#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000239# Platform support for MacOS
240#
Fred Drakec70b4482000-07-09 16:45:56 +0000241
242try:
243 import ic
244except ImportError:
245 pass
246else:
247 class InternetConfig:
Eric S. Raymondcfa40962001-01-23 15:49:34 +0000248 def open(self, url, new=0, autoraise=1):
Guido van Rossum2595a832000-11-13 20:30:57 +0000249 ic.launchurl(url)
Fred Drakec70b4482000-07-09 16:45:56 +0000250
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000251 def open_new(self, url): # Deprecated. May be removed in 2.1.
Fred Drakec70b4482000-07-09 16:45:56 +0000252 self.open(url)
253
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000254 # internet-config is the only supported controller on MacOS,
255 # so don't mess with the default!
256 _tryorder = ("internet-config")
Fred Drakec70b4482000-07-09 16:45:56 +0000257 register("internet-config", InternetConfig)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000258
259# OK, now that we know what the default preference orders for each
260# platform are, allow user to override them with the BROWSER variable.
261#
262if os.environ.has_key("BROWSER"):
263 # It's the user's responsibility to register handlers for any unknown
264 # browser referenced by this value, before calling open().
265 _tryorder = os.environ["BROWSER"].split(":")
266else:
267 # Optimization: filter out alternatives that aren't available, so we can
268 # avoid has_key() tests at runtime. (This may also allow some unused
269 # classes and class-instance storage to be garbage-collected.)
Jeremy Hylton8016a4b2001-02-27 18:44:14 +0000270 _tryorder = filter(lambda x: _browsers.has_key(x.lower())
271 or x.find("%s") > -1, _tryorder)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000272
273# end