webbrowser.py revision f7eb4faf38fc9e4a215acae3323140b99cdce08f
1"""Interfaces for launching and remotely controlling Web browsers."""
2
3import os
4import sys
5
6__all__ = ["Error", "open", "get", "register"]
7
8class Error(Exception):
9    pass
10
11_browsers = {}          # Dictionary of available browser controllers
12_tryorder = []          # Preference order of available browsers
13
14def register(name, klass, instance=None):
15    """Register a browser connector and, optionally, connection."""
16    _browsers[name.lower()] = [klass, instance]
17
18def 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 GenericBrowser(browser)
28        else:
29            # User gave us a browser name.
30            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")
36
37# Please note: the following definition hides a builtin function.
38
39def open(url, new=0, autoraise=1):
40    get().open(url, new, autoraise)
41
42def open_new(url):      # Marked deprecated.  May be removed in 2.1.
43    get().open(url, 1)
44
45#
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#
51
52#
53# Platform support for Unix
54#
55
56# 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
62    _tryorder = ("mozilla","netscape","kfm","grail","links","lynx","w3m")
63
64    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:
68            return 0
69        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
74
75    class GenericBrowser:
76        def __init__(self, cmd):
77            self.command = cmd
78
79        def open(self, url, new=0, autoraise=1):
80            os.system(self.command % url)
81
82        def open_new(self, url):        # Deprecated.  May be removed in 2.1.
83            self.open(url)
84
85    # 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"))
96
97    # X browsers have more in the way of options
98    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."
103                def __init__(self, name):
104                    self.name = name
105
106                def _remote(self, action, autoraise):
107                    raise_opt = ("-noraise", "-raise")[autoraise]
108                    cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name,
109                                                                  raise_opt,
110                                                                  action)
111                    rc = os.system(cmd)
112                    if rc:
113                        import time
114                        os.system("%s &" % self.name)
115                        time.sleep(PROCESS_CREATION_DELAY)
116                        rc = os.system(cmd)
117                    return not rc
118
119                def open(self, url, new=0, autoraise=1):
120                    if new:
121                        self._remote("openURL(%s, new-window)"%url, autoraise)
122                    else:
123                        self._remote("openURL(%s)" % url, autoraise)
124
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.
139        if _iscommand("kfm") or _iscommand("konqueror"):
140            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
152                        if _iscommand("konqueror"):
153                            os.system("konqueror --silent &")
154                        else:
155                            os.system("kfm -d &")
156                        time.sleep(PROCESS_CREATION_DELAY)
157                        rc = os.system(cmd)
158                    return not rc
159
160                def open(self, url, new=1, autoraise=1):
161                    # XXX Currently I know no way to prevent KFM from
162                    # opening a new win.
163                    self._remote("openURL %s" % url)
164
165                # Deprecated.  May be removed in 2.1.
166                open_new = open
167
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
181                    tempdir = os.path.join(tempfile.gettempdir(),
182                                           ".grail-unix")
183                    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
210                def open(self, url, new=0, autoraise=1):
211                    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#
225
226if sys.platform[:3] == "win":
227    _tryorder = ("netscape", "windows-default")
228
229    class WindowsDefault:
230        def open(self, url, new=0, autoraise=1):
231            os.startfile(url)
232
233        def open_new(self, url):        # Deprecated.  May be removed in 2.1.
234            self.open(url)
235
236    register("windows-default", WindowsDefault)
237
238#
239# Platform support for MacOS
240#
241
242try:
243    import ic
244except ImportError:
245    pass
246else:
247    class InternetConfig:
248        def open(self, url, new=0, autoraise=1):
249            ic.launchurl(url)
250
251        def open_new(self, url):        # Deprecated.  May be removed in 2.1.
252            self.open(url)
253
254    # internet-config is the only supported controller on MacOS,
255    # so don't mess with the default!
256    _tryorder = ("internet-config")
257    register("internet-config", InternetConfig)
258
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.)
270    _tryorder = filter(lambda x: _browsers.has_key(x.lower())
271                       or x.find("%s") > -1, _tryorder)
272
273# end
274