1 """Interfaces for launching and remotely controlling Web browsers."""
6 __all__
= ["Error", "open", "get", "register"]
8 class Error(Exception):
11 _browsers
= {} # Dictionary of available browser controllers
12 _tryorder
= [] # Preference order of available browsers
14 def register(name
, klass
, instance
=None):
15 """Register a browser connector and, optionally, connection."""
16 _browsers
[name
.lower()] = [klass
, instance
]
19 """Return a browser launcher instance appropriate for the environment."""
21 alternatives
= [using
]
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 GenericBrowser(browser
)
29 # User gave us a browser name.
31 command
= _browsers
[browser
.lower()]
33 command
= _synthesize(browser
)
34 if command
[1] is None:
38 raise Error("could not locate runnable browser")
40 # Please note: the following definition hides a builtin function.
42 def open(url
, new
=0, autoraise
=1):
43 get().open(url
, new
, autoraise
)
49 def _synthesize(browser
):
50 """Attempt to synthesize a controller base on existing controllers.
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
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].
61 if not os
.path
.exists(browser
):
63 name
= os
.path
.basename(browser
)
65 command
= _browsers
[name
.lower()]
68 # now attempt to clone to fit the new name:
69 controller
= command
[1]
70 if controller
and name
.lower() == controller
.basename
:
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
]
81 """Return True if cmd can be found on the executable search path."""
82 path
= os
.environ
.get("PATH")
85 for d
in path
.split(os
.pathsep
):
86 exe
= os
.path
.join(d
, cmd
)
87 if os
.path
.isfile(exe
):
92 PROCESS_CREATION_DELAY
= 4
96 def __init__(self
, cmd
):
97 self
.name
, self
.args
= cmd
.split(None, 1)
98 self
.basename
= os
.path
.basename(self
.name
)
100 def open(self
, url
, new
=0, autoraise
=1):
101 assert "'" not in url
102 command
= "%s %s" % (self
.name
, self
.args
)
103 os
.system(command
% url
)
105 def open_new(self
, url
):
110 "Launcher class for Netscape browsers."
111 def __init__(self
, name
):
113 self
.basename
= os
.path
.basename(name
)
115 def _remote(self
, action
, autoraise
):
116 raise_opt
= ("-noraise", "-raise")[autoraise
]
117 cmd
= "%s %s -remote '%s' >/dev/null 2>&1" % (self
.name
,
123 os
.system("%s &" % self
.name
)
124 time
.sleep(PROCESS_CREATION_DELAY
)
128 def open(self
, url
, new
=0, autoraise
=1):
130 self
._remote
("openURL(%s, new-window)"%url
, autoraise
)
132 self
._remote
("openURL(%s)" % url
, autoraise
)
134 def open_new(self
, url
):
139 """Launcher class for Galeon browsers."""
140 def __init__(self
, name
):
142 self
.basename
= os
.path
.basename(name
)
144 def _remote(self
, action
, autoraise
):
145 raise_opt
= ("--noraise", "")[autoraise
]
146 cmd
= "%s %s %s >/dev/null 2>&1" % (self
.name
, raise_opt
, action
)
150 os
.system("%s >/dev/null 2>&1 &" % self
.name
)
151 time
.sleep(PROCESS_CREATION_DELAY
)
155 def open(self
, url
, new
=0, autoraise
=1):
157 self
._remote
("-w '%s'" % url
, autoraise
)
159 self
._remote
("-n '%s'" % url
, autoraise
)
161 def open_new(self
, url
):
166 """Controller for the KDE File Manager (kfm, or Konqueror).
168 See http://developer.kde.org/documentation/other/kfmclient.html
169 for more information on the Konqueror remote-control interface.
173 if _iscommand("konqueror"):
174 self
.name
= self
.basename
= "konqueror"
176 self
.name
= self
.basename
= "kfm"
178 def _remote(self
, action
):
179 cmd
= "kfmclient %s >/dev/null 2>&1" % action
183 if self
.basename
== "konqueror":
184 os
.system(self
.name
+ " --silent &")
186 os
.system(self
.name
+ " -d &")
187 time
.sleep(PROCESS_CREATION_DELAY
)
191 def open(self
, url
, new
=1, autoraise
=1):
192 # XXX Currently I know no way to prevent KFM from
194 assert "'" not in url
195 self
._remote
("openURL '%s'" % url
)
201 # There should be a way to maintain a connection to Grail, but the
202 # Grail remote control protocol doesn't really allow that at this
203 # point. It probably neverwill!
204 def _find_grail_rc(self
):
209 tempdir
= os
.path
.join(tempfile
.gettempdir(),
211 user
= pwd
.getpwuid(os
.getuid())[0]
212 filename
= os
.path
.join(tempdir
, user
+ "-*")
213 maybes
= glob
.glob(filename
)
216 s
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
218 # need to PING each one until we find one that's live
222 # no good; attempt to clean it out, but don't fail:
230 def _remote(self
, action
):
231 s
= self
._find
_grail
_rc
()
238 def open(self
, url
, new
=0, autoraise
=1):
240 self
._remote
("LOADNEW " + url
)
242 self
._remote
("LOAD " + url
)
244 def open_new(self
, url
):
248 class WindowsDefault
:
249 def open(self
, url
, new
=0, autoraise
=1):
252 def open_new(self
, url
):
256 # Platform support for Unix
259 # This is the right test because all these Unix browsers require either
260 # a console terminal of an X display to run. Note that we cannot split
261 # the TERM and DISPLAY cases, because we might be running Python from inside
263 if os
.environ
.get("TERM") or os
.environ
.get("DISPLAY"):
264 _tryorder
= ["galeon", "mozilla", "netscape", "kfm",
265 "grail", "links", "lynx", "w3m",]
267 # Easy cases first -- register console browsers if we have them.
268 if os
.environ
.get("TERM"):
269 # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
270 if _iscommand("links"):
271 register("links", None, GenericBrowser("links '%s'"))
272 # The Lynx browser <http://lynx.browser.org/>
273 if _iscommand("lynx"):
274 register("lynx", None, GenericBrowser("lynx '%s'"))
275 # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
276 if _iscommand("w3m"):
277 register("w3m", None, GenericBrowser("w3m '%s'"))
279 # X browsers have more in the way of options
280 if os
.environ
.get("DISPLAY"):
281 # First, the Netscape series
282 if _iscommand("mozilla"):
283 register("mozilla", None, Netscape("mozilla"))
284 if _iscommand("netscape"):
285 register("netscape", None, Netscape("netscape"))
287 # Next, Mosaic -- old but still in use.
288 if _iscommand("mosaic"):
289 register("mosaic", None, GenericBrowser(
290 "mosaic '%s' >/dev/null &"))
293 if _iscommand("galeon"):
294 register("galeon", None, Galeon("galeon"))
296 # Konqueror/kfm, the KDE browser.
297 if _iscommand("kfm") or _iscommand("konqueror"):
298 register("kfm", Konqueror
, Konqueror())
300 # Grail, the Python browser.
301 if _iscommand("grail"):
302 register("grail", Grail
, None)
305 class InternetConfig
:
306 def open(self
, url
, new
=0, autoraise
=1):
309 def open_new(self
, url
):
314 # Platform support for Windows
317 if sys
.platform
[:3] == "win":
318 _tryorder
= ["netscape", "windows-default"]
319 register("windows-default", WindowsDefault
)
322 # Platform support for MacOS
330 # internet-config is the only supported controller on MacOS,
331 # so don't mess with the default!
332 _tryorder
= ["internet-config"]
333 register("internet-config", InternetConfig
)
336 # Platform support for OS/2
339 if sys
.platform
[:3] == "os2" and _iscommand("netscape.exe"):
340 _tryorder
= ["os2netscape"]
341 register("os2netscape", None,
342 GenericBrowser("start netscape.exe %s"))
344 # OK, now that we know what the default preference orders for each
345 # platform are, allow user to override them with the BROWSER variable.
347 if "BROWSER" in os
.environ
:
348 # It's the user's responsibility to register handlers for any unknown
349 # browser referenced by this value, before calling open().
350 _tryorder
= os
.environ
["BROWSER"].split(os
.pathsep
)
352 for cmd
in _tryorder
:
353 if not cmd
.lower() in _browsers
:
354 if _iscommand(cmd
.lower()):
355 register(cmd
.lower(), None, GenericBrowser(
356 "%s '%%s'" % cmd
.lower()))
357 cmd
= None # to make del work if _tryorder was empty
360 _tryorder
= filter(lambda x
: x
.lower() in _browsers
361 or x
.find("%s") > -1, _tryorder
)
362 # what to do if _tryorder is now empty?