webbrowser.py 11 KB
Newer Older
1
"""Interfaces for launching and remotely controlling Web browsers."""
2 3 4 5

import os
import sys

6 7
__all__ = ["Error", "open", "get", "register"]

8 9 10
class Error(Exception):
    pass

Tim Peters's avatar
Tim Peters committed
11 12
_browsers = {}          # Dictionary of available browser controllers
_tryorder = []          # Preference order of available browsers
13 14 15 16 17

def register(name, klass, instance=None):
    """Register a browser connector and, optionally, connection."""
    _browsers[name.lower()] = [klass, instance]

18 19
def get(using=None):
    """Return a browser launcher instance appropriate for the environment."""
20
    if using is not None:
21 22 23 24
        alternatives = [using]
    else:
        alternatives = _tryorder
    for browser in alternatives:
25
        if '%s' in browser:
26
            # User gave us a command line, don't mess with it.
27
            return GenericBrowser(browser)
28
        else:
Tim Peters's avatar
Tim Peters committed
29
            # User gave us a browser name.
30 31 32 33
            try:
                command = _browsers[browser.lower()]
            except KeyError:
                command = _synthesize(browser)
34 35 36 37 38
            if command[1] is None:
                return command[0]()
            else:
                return command[1]
    raise Error("could not locate runnable browser")
39 40 41

# Please note: the following definition hides a builtin function.

42 43
def open(url, new=0, autoraise=1):
    get().open(url, new, autoraise)
44

Fred Drake's avatar
Fred Drake committed
45
def open_new(url):
46
    get().open(url, 1)
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

def _synthesize(browser):
    """Attempt to synthesize a controller base on existing controllers.

    This is useful to create a controller when a user specifies a path to
    an entry in the BROWSER environment variable -- we can copy a general
    controller to operate using a specific installation of the desired
    browser in this way.

    If we can't create a controller in this way, or if there is no
    executable for the requested browser, return [None, None].

    """
    if not os.path.exists(browser):
        return [None, None]
    name = os.path.basename(browser)
    try:
        command = _browsers[name.lower()]
    except KeyError:
        return [None, None]
    # now attempt to clone to fit the new name:
    controller = command[1]
    if controller and name.lower() == controller.basename:
        import copy
        controller = copy.copy(controller)
        controller.name = browser
        controller.basename = os.path.basename(browser)
        register(browser, None, controller)
        return [None, controller]
77
    return [None, None]
78

Fred Drake's avatar
Fred Drake committed
79 80

def _iscommand(cmd):
81
    """Return True if cmd can be found on the executable search path."""
Fred Drake's avatar
Fred Drake committed
82 83
    path = os.environ.get("PATH")
    if not path:
84
        return False
Fred Drake's avatar
Fred Drake committed
85 86 87
    for d in path.split(os.pathsep):
        exe = os.path.join(d, cmd)
        if os.path.isfile(exe):
88 89
            return True
    return False
Fred Drake's avatar
Fred Drake committed
90 91 92 93 94 95 96 97 98 99 100


PROCESS_CREATION_DELAY = 4


class GenericBrowser:
    def __init__(self, cmd):
        self.name, self.args = cmd.split(None, 1)
        self.basename = os.path.basename(self.name)

    def open(self, url, new=0, autoraise=1):
101
        assert "'" not in url
Fred Drake's avatar
Fred Drake committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
        command = "%s %s" % (self.name, self.args)
        os.system(command % url)

    def open_new(self, url):
        self.open(url)


class Netscape:
    "Launcher class for Netscape browsers."
    def __init__(self, name):
        self.name = name
        self.basename = os.path.basename(name)

    def _remote(self, action, autoraise):
        raise_opt = ("-noraise", "-raise")[autoraise]
        cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name,
                                                      raise_opt,
                                                      action)
        rc = os.system(cmd)
        if rc:
            import time
            os.system("%s &" % self.name)
            time.sleep(PROCESS_CREATION_DELAY)
            rc = os.system(cmd)
        return not rc

    def open(self, url, new=0, autoraise=1):
        if new:
            self._remote("openURL(%s, new-window)"%url, autoraise)
        else:
            self._remote("openURL(%s)" % url, autoraise)

    def open_new(self, url):
        self.open(url, 1)


138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
class Galeon:
    """Launcher class for Galeon browsers."""
    def __init__(self, name):
        self.name = name
        self.basename = os.path.basename(name)

    def _remote(self, action, autoraise):
        raise_opt = ("--noraise", "")[autoraise]
        cmd = "%s %s %s >/dev/null 2>&1" % (self.name, raise_opt, action)
        rc = os.system(cmd)
        if rc:
            import time
            os.system("%s >/dev/null 2>&1 &" % self.name)
            time.sleep(PROCESS_CREATION_DELAY)
            rc = os.system(cmd)
        return not rc

    def open(self, url, new=0, autoraise=1):
        if new:
            self._remote("-w '%s'" % url, autoraise)
        else:
            self._remote("-n '%s'" % url, autoraise)

    def open_new(self, url):
        self.open(url, 1)


Fred Drake's avatar
Fred Drake committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178
class Konqueror:
    """Controller for the KDE File Manager (kfm, or Konqueror).

    See http://developer.kde.org/documentation/other/kfmclient.html
    for more information on the Konqueror remote-control interface.

    """
    def __init__(self):
        if _iscommand("konqueror"):
            self.name = self.basename = "konqueror"
        else:
            self.name = self.basename = "kfm"

    def _remote(self, action):
179
        cmd = "kfmclient %s >/dev/null 2>&1" % action
Fred Drake's avatar
Fred Drake committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193
        rc = os.system(cmd)
        if rc:
            import time
            if self.basename == "konqueror":
                os.system(self.name + " --silent &")
            else:
                os.system(self.name + " -d &")
            time.sleep(PROCESS_CREATION_DELAY)
            rc = os.system(cmd)
        return not rc

    def open(self, url, new=1, autoraise=1):
        # XXX Currently I know no way to prevent KFM from
        # opening a new win.
194
        assert "'" not in url
195
        self._remote("openURL '%s'" % url)
Fred Drake's avatar
Fred Drake committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

    open_new = open


class Grail:
    # There should be a way to maintain a connection to Grail, but the
    # Grail remote control protocol doesn't really allow that at this
    # point.  It probably neverwill!
    def _find_grail_rc(self):
        import glob
        import pwd
        import socket
        import tempfile
        tempdir = os.path.join(tempfile.gettempdir(),
                               ".grail-unix")
211
        user = pwd.getpwuid(os.getuid())[0]
Fred Drake's avatar
Fred Drake committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
        filename = os.path.join(tempdir, user + "-*")
        maybes = glob.glob(filename)
        if not maybes:
            return None
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        for fn in maybes:
            # need to PING each one until we find one that's live
            try:
                s.connect(fn)
            except socket.error:
                # no good; attempt to clean it out, but don't fail:
                try:
                    os.unlink(fn)
                except IOError:
                    pass
            else:
                return s

    def _remote(self, action):
        s = self._find_grail_rc()
        if not s:
            return 0
        s.send(action)
        s.close()
        return 1

    def open(self, url, new=0, autoraise=1):
        if new:
            self._remote("LOADNEW " + url)
        else:
            self._remote("LOAD " + url)

    def open_new(self, url):
        self.open(url, 1)


class WindowsDefault:
    def open(self, url, new=0, autoraise=1):
        os.startfile(url)

    def open_new(self, url):
        self.open(url)
254

Tim Peters's avatar
Tim Peters committed
255
#
256 257
# Platform support for Unix
#
258

259 260 261 262 263
# This is the right test because all these Unix browsers require either
# a console terminal of an X display to run.  Note that we cannot split
# the TERM and DISPLAY cases, because we might be running Python from inside
# an xterm.
if os.environ.get("TERM") or os.environ.get("DISPLAY"):
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
264
    _tryorder = ["links", "lynx", "w3m"]
265 266 267 268 269

    # Easy cases first -- register console browsers if we have them.
    if os.environ.get("TERM"):
        # The Links browser <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
        if _iscommand("links"):
270
            register("links", None, GenericBrowser("links '%s'"))
271 272
        # The Lynx browser <http://lynx.browser.org/>
        if _iscommand("lynx"):
273
            register("lynx", None, GenericBrowser("lynx '%s'"))
274 275
        # The w3m browser <http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/>
        if _iscommand("w3m"):
276
            register("w3m", None, GenericBrowser("w3m '%s'"))
277

278
    # X browsers have more in the way of options
279
    if os.environ.get("DISPLAY"):
280 281
        _tryorder = ["galeon", "skipstone",
                     "mozilla-firefox", "mozilla-firebird", "mozilla", "netscape",
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
282 283
                     "kfm", "grail"] + _tryorder

284
        # First, the Netscape series
285 286 287 288
        for browser in ("mozilla-firefox", "mozilla-firebird",
                        "mozilla", "netscape"):
            if _iscommand(browser):
                register(browser, None, Netscape(browser))
289 290 291

        # Next, Mosaic -- old but still in use.
        if _iscommand("mosaic"):
292 293
            register("mosaic", None, GenericBrowser(
                "mosaic '%s' >/dev/null &"))
294

295 296 297 298
        # Gnome's Galeon
        if _iscommand("galeon"):
            register("galeon", None, Galeon("galeon"))

Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
299 300 301 302 303
        # Skipstone, another Gtk/Mozilla based browser
        if _iscommand("skipstone"):
            register("skipstone", None, GenericBrowser(
                "skipstone '%s' >/dev/null &"))

304
        # Konqueror/kfm, the KDE browser.
305
        if _iscommand("kfm") or _iscommand("konqueror"):
306
            register("kfm", Konqueror, Konqueror())
307 308 309 310

        # Grail, the Python browser.
        if _iscommand("grail"):
            register("grail", Grail, None)
311

Fred Drake's avatar
Fred Drake committed
312 313 314 315 316 317 318 319 320

class InternetConfig:
    def open(self, url, new=0, autoraise=1):
        ic.launchurl(url)

    def open_new(self, url):
        self.open(url)


321 322 323
#
# Platform support for Windows
#
324

325
if sys.platform[:3] == "win":
326
    _tryorder = ["netscape", "windows-default"]
327 328 329
    register("windows-default", WindowsDefault)

#
330 331
# Platform support for MacOS
#
332 333 334 335 336 337

try:
    import ic
except ImportError:
    pass
else:
338 339
    # internet-config is the only supported controller on MacOS,
    # so don't mess with the default!
340
    _tryorder = ["internet-config"]
341
    register("internet-config", InternetConfig)
342

343 344 345 346 347
#
# Platform support for OS/2
#

if sys.platform[:3] == "os2" and _iscommand("netscape.exe"):
348
    _tryorder = ["os2netscape"]
349 350 351
    register("os2netscape", None,
             GenericBrowser("start netscape.exe %s"))

352 353 354
# OK, now that we know what the default preference orders for each
# platform are, allow user to override them with the BROWSER variable.
#
355
if "BROWSER" in os.environ:
356 357
    # It's the user's responsibility to register handlers for any unknown
    # browser referenced by this value, before calling open().
358
    _tryorder = os.environ["BROWSER"].split(os.pathsep)
359 360

for cmd in _tryorder:
361
    if not cmd.lower() in _browsers:
362
        if _iscommand(cmd.lower()):
363 364
            register(cmd.lower(), None, GenericBrowser(
                "%s '%%s'" % cmd.lower()))
365
cmd = None # to make del work if _tryorder was empty
366
del cmd
367

368
_tryorder = filter(lambda x: x.lower() in _browsers
369 370
                   or x.find("%s") > -1, _tryorder)
# what to do if _tryorder is now empty?