blob: b562ab3d191d78650631abdda013c01efd421cfa [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."""
Raymond Hettinger10ff7062002-06-02 03:04:52 +000020 if using is not None:
Eric S. Raymondf7f18512001-01-23 13:16:32 +000021 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
Fred Drake3f8f1642001-07-19 03:46:26 +000045def open_new(url):
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]
Andrew M. Kuchling118aa532001-08-13 14:37:23 +000077 return [None, None]
Fred Drakef4e5bd92001-04-12 22:07:27 +000078
Fred Drake3f8f1642001-07-19 03:46:26 +000079
80def _iscommand(cmd):
Tim Petersbc0e9102002-04-04 22:55:58 +000081 """Return True if cmd can be found on the executable search path."""
Fred Drake3f8f1642001-07-19 03:46:26 +000082 path = os.environ.get("PATH")
83 if not path:
Tim Petersbc0e9102002-04-04 22:55:58 +000084 return False
Fred Drake3f8f1642001-07-19 03:46:26 +000085 for d in path.split(os.pathsep):
86 exe = os.path.join(d, cmd)
87 if os.path.isfile(exe):
Tim Petersbc0e9102002-04-04 22:55:58 +000088 return True
89 return False
Fred Drake3f8f1642001-07-19 03:46:26 +000090
91
92PROCESS_CREATION_DELAY = 4
93
94
95class GenericBrowser:
96 def __init__(self, cmd):
97 self.name, self.args = cmd.split(None, 1)
98 self.basename = os.path.basename(self.name)
99
100 def open(self, url, new=0, autoraise=1):
Fred Drake925f1442002-01-07 15:29:01 +0000101 assert "'" not in url
Fred Drake3f8f1642001-07-19 03:46:26 +0000102 command = "%s %s" % (self.name, self.args)
103 os.system(command % url)
104
105 def open_new(self, url):
106 self.open(url)
107
108
109class Netscape:
110 "Launcher class for Netscape browsers."
111 def __init__(self, name):
112 self.name = name
113 self.basename = os.path.basename(name)
114
115 def _remote(self, action, autoraise):
116 raise_opt = ("-noraise", "-raise")[autoraise]
117 cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name,
118 raise_opt,
119 action)
120 rc = os.system(cmd)
121 if rc:
122 import time
123 os.system("%s &" % self.name)
124 time.sleep(PROCESS_CREATION_DELAY)
125 rc = os.system(cmd)
126 return not rc
127
128 def open(self, url, new=0, autoraise=1):
129 if new:
130 self._remote("openURL(%s, new-window)"%url, autoraise)
131 else:
132 self._remote("openURL(%s)" % url, autoraise)
133
134 def open_new(self, url):
135 self.open(url, 1)
136
137
138class Konqueror:
139 """Controller for the KDE File Manager (kfm, or Konqueror).
140
141 See http://developer.kde.org/documentation/other/kfmclient.html
142 for more information on the Konqueror remote-control interface.
143
144 """
145 def __init__(self):
146 if _iscommand("konqueror"):
147 self.name = self.basename = "konqueror"
148 else:
149 self.name = self.basename = "kfm"
150
151 def _remote(self, action):
Fred Drake925f1442002-01-07 15:29:01 +0000152 assert "'" not in action
153 cmd = "kfmclient '%s' >/dev/null 2>&1" % action
Fred Drake3f8f1642001-07-19 03:46:26 +0000154 rc = os.system(cmd)
155 if rc:
156 import time
157 if self.basename == "konqueror":
158 os.system(self.name + " --silent &")
159 else:
160 os.system(self.name + " -d &")
161 time.sleep(PROCESS_CREATION_DELAY)
162 rc = os.system(cmd)
163 return not rc
164
165 def open(self, url, new=1, autoraise=1):
166 # XXX Currently I know no way to prevent KFM from
167 # opening a new win.
Fred Drake925f1442002-01-07 15:29:01 +0000168 self._remote("openURL '%s'" % url)
Fred Drake3f8f1642001-07-19 03:46:26 +0000169
170 open_new = open
171
172
173class Grail:
174 # There should be a way to maintain a connection to Grail, but the
175 # Grail remote control protocol doesn't really allow that at this
176 # point. It probably neverwill!
177 def _find_grail_rc(self):
178 import glob
179 import pwd
180 import socket
181 import tempfile
182 tempdir = os.path.join(tempfile.gettempdir(),
183 ".grail-unix")
Fred Drake16623fe2001-10-13 16:00:52 +0000184 user = pwd.getpwuid(os.getuid())[0]
Fred Drake3f8f1642001-07-19 03:46:26 +0000185 filename = os.path.join(tempdir, user + "-*")
186 maybes = glob.glob(filename)
187 if not maybes:
188 return None
189 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
190 for fn in maybes:
191 # need to PING each one until we find one that's live
192 try:
193 s.connect(fn)
194 except socket.error:
195 # no good; attempt to clean it out, but don't fail:
196 try:
197 os.unlink(fn)
198 except IOError:
199 pass
200 else:
201 return s
202
203 def _remote(self, action):
204 s = self._find_grail_rc()
205 if not s:
206 return 0
207 s.send(action)
208 s.close()
209 return 1
210
211 def open(self, url, new=0, autoraise=1):
212 if new:
213 self._remote("LOADNEW " + url)
214 else:
215 self._remote("LOAD " + url)
216
217 def open_new(self, url):
218 self.open(url, 1)
219
220
221class WindowsDefault:
222 def open(self, url, new=0, autoraise=1):
223 os.startfile(url)
224
225 def open_new(self, url):
226 self.open(url)
Fred Drakec70b4482000-07-09 16:45:56 +0000227
Tim Peters658cba62001-02-09 20:06:00 +0000228#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000229# Platform support for Unix
230#
Fred Drakec70b4482000-07-09 16:45:56 +0000231
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000232# This is the right test because all these Unix browsers require either
233# a console terminal of an X display to run. Note that we cannot split
234# the TERM and DISPLAY cases, because we might be running Python from inside
235# an xterm.
236if os.environ.get("TERM") or os.environ.get("DISPLAY"):
Guido van Rossumcb331652001-12-03 15:51:31 +0000237 _tryorder = ["mozilla","netscape","kfm","grail","links","lynx","w3m"]
Fred Drakec70b4482000-07-09 16:45:56 +0000238
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000239 # Easy cases first -- register console browsers if we have them.
240 if os.environ.get("TERM"):
241 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
242 if _iscommand("links"):
Fred Drake925f1442002-01-07 15:29:01 +0000243 register("links", None, GenericBrowser("links '%s'"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000244 # The Lynx browser <http://lynx.browser.org/>
245 if _iscommand("lynx"):
Fred Drake925f1442002-01-07 15:29:01 +0000246 register("lynx", None, GenericBrowser("lynx '%s'"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000247 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
248 if _iscommand("w3m"):
Fred Drake925f1442002-01-07 15:29:01 +0000249 register("w3m", None, GenericBrowser("w3m '%s'"))
Fred Drakec70b4482000-07-09 16:45:56 +0000250
Ka-Ping Yee0a8c29b2001-03-02 02:01:40 +0000251 # X browsers have more in the way of options
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000252 if os.environ.get("DISPLAY"):
253 # First, the Netscape series
Fred Drake925f1442002-01-07 15:29:01 +0000254 if _iscommand("mozilla"):
255 register("mozilla", None, Netscape("mozilla"))
256 if _iscommand("netscape"):
257 register("netscape", None, Netscape("netscape"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000258
259 # Next, Mosaic -- old but still in use.
260 if _iscommand("mosaic"):
Fred Drake925f1442002-01-07 15:29:01 +0000261 register("mosaic", None, GenericBrowser(
262 "mosaic '%s' >/dev/null &"))
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000263
264 # Konqueror/kfm, the KDE browser.
Fred Drakefc31f262001-03-26 15:06:15 +0000265 if _iscommand("kfm") or _iscommand("konqueror"):
Fred Drakef4e5bd92001-04-12 22:07:27 +0000266 register("kfm", Konqueror, Konqueror())
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000267
268 # Grail, the Python browser.
269 if _iscommand("grail"):
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000270 register("grail", Grail, None)
271
Fred Drake3f8f1642001-07-19 03:46:26 +0000272
273class InternetConfig:
274 def open(self, url, new=0, autoraise=1):
275 ic.launchurl(url)
276
277 def open_new(self, url):
278 self.open(url)
279
280
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000281#
282# Platform support for Windows
283#
Fred Drakec70b4482000-07-09 16:45:56 +0000284
285if sys.platform[:3] == "win":
Guido van Rossumcb331652001-12-03 15:51:31 +0000286 _tryorder = ["netscape", "windows-default"]
Fred Drakec70b4482000-07-09 16:45:56 +0000287 register("windows-default", WindowsDefault)
Fred Drakec70b4482000-07-09 16:45:56 +0000288
Fred Drakec70b4482000-07-09 16:45:56 +0000289#
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000290# Platform support for MacOS
291#
Fred Drakec70b4482000-07-09 16:45:56 +0000292
293try:
294 import ic
295except ImportError:
296 pass
297else:
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000298 # internet-config is the only supported controller on MacOS,
299 # so don't mess with the default!
Guido van Rossumcb331652001-12-03 15:51:31 +0000300 _tryorder = ["internet-config"]
Fred Drakec70b4482000-07-09 16:45:56 +0000301 register("internet-config", InternetConfig)
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000302
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000303#
304# Platform support for OS/2
305#
306
307if sys.platform[:3] == "os2" and _iscommand("netscape.exe"):
Guido van Rossumcb331652001-12-03 15:51:31 +0000308 _tryorder = ["os2netscape"]
Martin v. Löwis3a89b2b2001-11-25 14:35:58 +0000309 register("os2netscape", None,
310 GenericBrowser("start netscape.exe %s"))
311
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000312# OK, now that we know what the default preference orders for each
313# platform are, allow user to override them with the BROWSER variable.
314#
Raymond Hettinger54f02222002-06-01 14:18:47 +0000315if "BROWSER" in os.environ:
Eric S. Raymondf7f18512001-01-23 13:16:32 +0000316 # It's the user's responsibility to register handlers for any unknown
317 # browser referenced by this value, before calling open().
Guido van Rossum4b402f22001-12-04 17:43:22 +0000318 _tryorder = os.environ["BROWSER"].split(os.pathsep)
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000319
320for cmd in _tryorder:
Raymond Hettinger54f02222002-06-01 14:18:47 +0000321 if not cmd.lower() in _browsers:
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000322 if _iscommand(cmd.lower()):
Fred Drake925f1442002-01-07 15:29:01 +0000323 register(cmd.lower(), None, GenericBrowser(
324 "%s '%%s'" % cmd.lower()))
Jack Jansenff0a7b82002-03-15 13:47:32 +0000325cmd = None # to make del work if _tryorder was empty
Neal Norwitzf963b452002-02-11 18:11:09 +0000326del cmd
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000327
Raymond Hettinger54f02222002-06-01 14:18:47 +0000328_tryorder = filter(lambda x: x.lower() in _browsers
Skip Montanarocdab3bf2001-07-18 20:03:32 +0000329 or x.find("%s") > -1, _tryorder)
330# what to do if _tryorder is now empty?