+++ /dev/null
-#! /usr/bin/env python\r
-"""Interfaces for launching and remotely controlling Web browsers."""\r
-# Maintained by Georg Brandl.\r
-\r
-import os\r
-import shlex\r
-import sys\r
-import stat\r
-import subprocess\r
-import time\r
-\r
-__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]\r
-\r
-class Error(Exception):\r
- pass\r
-\r
-_browsers = {} # Dictionary of available browser controllers\r
-_tryorder = [] # Preference order of available browsers\r
-\r
-def register(name, klass, instance=None, update_tryorder=1):\r
- """Register a browser connector and, optionally, connection."""\r
- _browsers[name.lower()] = [klass, instance]\r
- if update_tryorder > 0:\r
- _tryorder.append(name)\r
- elif update_tryorder < 0:\r
- _tryorder.insert(0, name)\r
-\r
-def get(using=None):\r
- """Return a browser launcher instance appropriate for the environment."""\r
- if using is not None:\r
- alternatives = [using]\r
- else:\r
- alternatives = _tryorder\r
- for browser in alternatives:\r
- if '%s' in browser:\r
- # User gave us a command line, split it into name and args\r
- browser = shlex.split(browser)\r
- if browser[-1] == '&':\r
- return BackgroundBrowser(browser[:-1])\r
- else:\r
- return GenericBrowser(browser)\r
- else:\r
- # User gave us a browser name or path.\r
- try:\r
- command = _browsers[browser.lower()]\r
- except KeyError:\r
- command = _synthesize(browser)\r
- if command[1] is not None:\r
- return command[1]\r
- elif command[0] is not None:\r
- return command[0]()\r
- raise Error("could not locate runnable browser")\r
-\r
-# Please note: the following definition hides a builtin function.\r
-# It is recommended one does "import webbrowser" and uses webbrowser.open(url)\r
-# instead of "from webbrowser import *".\r
-\r
-def open(url, new=0, autoraise=True):\r
- for name in _tryorder:\r
- browser = get(name)\r
- if browser.open(url, new, autoraise):\r
- return True\r
- return False\r
-\r
-def open_new(url):\r
- return open(url, 1)\r
-\r
-def open_new_tab(url):\r
- return open(url, 2)\r
-\r
-\r
-def _synthesize(browser, update_tryorder=1):\r
- """Attempt to synthesize a controller base on existing controllers.\r
-\r
- This is useful to create a controller when a user specifies a path to\r
- an entry in the BROWSER environment variable -- we can copy a general\r
- controller to operate using a specific installation of the desired\r
- browser in this way.\r
-\r
- If we can't create a controller in this way, or if there is no\r
- executable for the requested browser, return [None, None].\r
-\r
- """\r
- cmd = browser.split()[0]\r
- if not _iscommand(cmd):\r
- return [None, None]\r
- name = os.path.basename(cmd)\r
- try:\r
- command = _browsers[name.lower()]\r
- except KeyError:\r
- return [None, None]\r
- # now attempt to clone to fit the new name:\r
- controller = command[1]\r
- if controller and name.lower() == controller.basename:\r
- import copy\r
- controller = copy.copy(controller)\r
- controller.name = browser\r
- controller.basename = os.path.basename(browser)\r
- register(browser, None, controller, update_tryorder)\r
- return [None, controller]\r
- return [None, None]\r
-\r
-\r
-if sys.platform[:3] == "win":\r
- def _isexecutable(cmd):\r
- cmd = cmd.lower()\r
- if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):\r
- return True\r
- for ext in ".exe", ".bat":\r
- if os.path.isfile(cmd + ext):\r
- return True\r
- return False\r
-else:\r
- def _isexecutable(cmd):\r
- if os.path.isfile(cmd):\r
- mode = os.stat(cmd)[stat.ST_MODE]\r
- if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:\r
- return True\r
- return False\r
-\r
-def _iscommand(cmd):\r
- """Return True if cmd is executable or can be found on the executable\r
- search path."""\r
- if _isexecutable(cmd):\r
- return True\r
- path = os.environ.get("PATH")\r
- if not path:\r
- return False\r
- for d in path.split(os.pathsep):\r
- exe = os.path.join(d, cmd)\r
- if _isexecutable(exe):\r
- return True\r
- return False\r
-\r
-\r
-# General parent classes\r
-\r
-class BaseBrowser(object):\r
- """Parent class for all browsers. Do not use directly."""\r
-\r
- args = ['%s']\r
-\r
- def __init__(self, name=""):\r
- self.name = name\r
- self.basename = name\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- raise NotImplementedError\r
-\r
- def open_new(self, url):\r
- return self.open(url, 1)\r
-\r
- def open_new_tab(self, url):\r
- return self.open(url, 2)\r
-\r
-\r
-class GenericBrowser(BaseBrowser):\r
- """Class for all browsers started with a command\r
- and without remote functionality."""\r
-\r
- def __init__(self, name):\r
- if isinstance(name, basestring):\r
- self.name = name\r
- self.args = ["%s"]\r
- else:\r
- # name should be a list with arguments\r
- self.name = name[0]\r
- self.args = name[1:]\r
- self.basename = os.path.basename(self.name)\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- cmdline = [self.name] + [arg.replace("%s", url)\r
- for arg in self.args]\r
- try:\r
- if sys.platform[:3] == 'win':\r
- p = subprocess.Popen(cmdline)\r
- else:\r
- p = subprocess.Popen(cmdline, close_fds=True)\r
- return not p.wait()\r
- except OSError:\r
- return False\r
-\r
-\r
-class BackgroundBrowser(GenericBrowser):\r
- """Class for all browsers which are to be started in the\r
- background."""\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- cmdline = [self.name] + [arg.replace("%s", url)\r
- for arg in self.args]\r
- try:\r
- if sys.platform[:3] == 'win':\r
- p = subprocess.Popen(cmdline)\r
- else:\r
- setsid = getattr(os, 'setsid', None)\r
- if not setsid:\r
- setsid = getattr(os, 'setpgrp', None)\r
- p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)\r
- return (p.poll() is None)\r
- except OSError:\r
- return False\r
-\r
-\r
-class UnixBrowser(BaseBrowser):\r
- """Parent class for all Unix browsers with remote functionality."""\r
-\r
- raise_opts = None\r
- remote_args = ['%action', '%s']\r
- remote_action = None\r
- remote_action_newwin = None\r
- remote_action_newtab = None\r
- background = False\r
- redirect_stdout = True\r
-\r
- def _invoke(self, args, remote, autoraise):\r
- raise_opt = []\r
- if remote and self.raise_opts:\r
- # use autoraise argument only for remote invocation\r
- autoraise = int(autoraise)\r
- opt = self.raise_opts[autoraise]\r
- if opt: raise_opt = [opt]\r
-\r
- cmdline = [self.name] + raise_opt + args\r
-\r
- if remote or self.background:\r
- inout = file(os.devnull, "r+")\r
- else:\r
- # for TTY browsers, we need stdin/out\r
- inout = None\r
- # if possible, put browser in separate process group, so\r
- # keyboard interrupts don't affect browser as well as Python\r
- setsid = getattr(os, 'setsid', None)\r
- if not setsid:\r
- setsid = getattr(os, 'setpgrp', None)\r
-\r
- p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,\r
- stdout=(self.redirect_stdout and inout or None),\r
- stderr=inout, preexec_fn=setsid)\r
- if remote:\r
- # wait five secons. If the subprocess is not finished, the\r
- # remote invocation has (hopefully) started a new instance.\r
- time.sleep(1)\r
- rc = p.poll()\r
- if rc is None:\r
- time.sleep(4)\r
- rc = p.poll()\r
- if rc is None:\r
- return True\r
- # if remote call failed, open() will try direct invocation\r
- return not rc\r
- elif self.background:\r
- if p.poll() is None:\r
- return True\r
- else:\r
- return False\r
- else:\r
- return not p.wait()\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- if new == 0:\r
- action = self.remote_action\r
- elif new == 1:\r
- action = self.remote_action_newwin\r
- elif new == 2:\r
- if self.remote_action_newtab is None:\r
- action = self.remote_action_newwin\r
- else:\r
- action = self.remote_action_newtab\r
- else:\r
- raise Error("Bad 'new' parameter to open(); " +\r
- "expected 0, 1, or 2, got %s" % new)\r
-\r
- args = [arg.replace("%s", url).replace("%action", action)\r
- for arg in self.remote_args]\r
- success = self._invoke(args, True, autoraise)\r
- if not success:\r
- # remote invocation failed, try straight way\r
- args = [arg.replace("%s", url) for arg in self.args]\r
- return self._invoke(args, False, False)\r
- else:\r
- return True\r
-\r
-\r
-class Mozilla(UnixBrowser):\r
- """Launcher class for Mozilla/Netscape browsers."""\r
-\r
- raise_opts = ["-noraise", "-raise"]\r
- remote_args = ['-remote', 'openURL(%s%action)']\r
- remote_action = ""\r
- remote_action_newwin = ",new-window"\r
- remote_action_newtab = ",new-tab"\r
- background = True\r
-\r
-Netscape = Mozilla\r
-\r
-\r
-class Galeon(UnixBrowser):\r
- """Launcher class for Galeon/Epiphany browsers."""\r
-\r
- raise_opts = ["-noraise", ""]\r
- remote_args = ['%action', '%s']\r
- remote_action = "-n"\r
- remote_action_newwin = "-w"\r
- background = True\r
-\r
-\r
-class Opera(UnixBrowser):\r
- "Launcher class for Opera browser."\r
-\r
- raise_opts = ["-noraise", ""]\r
- remote_args = ['-remote', 'openURL(%s%action)']\r
- remote_action = ""\r
- remote_action_newwin = ",new-window"\r
- remote_action_newtab = ",new-page"\r
- background = True\r
-\r
-\r
-class Elinks(UnixBrowser):\r
- "Launcher class for Elinks browsers."\r
-\r
- remote_args = ['-remote', 'openURL(%s%action)']\r
- remote_action = ""\r
- remote_action_newwin = ",new-window"\r
- remote_action_newtab = ",new-tab"\r
- background = False\r
-\r
- # elinks doesn't like its stdout to be redirected -\r
- # it uses redirected stdout as a signal to do -dump\r
- redirect_stdout = False\r
-\r
-\r
-class Konqueror(BaseBrowser):\r
- """Controller for the KDE File Manager (kfm, or Konqueror).\r
-\r
- See the output of ``kfmclient --commands``\r
- for more information on the Konqueror remote-control interface.\r
- """\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- # XXX Currently I know no way to prevent KFM from opening a new win.\r
- if new == 2:\r
- action = "newTab"\r
- else:\r
- action = "openURL"\r
-\r
- devnull = file(os.devnull, "r+")\r
- # if possible, put browser in separate process group, so\r
- # keyboard interrupts don't affect browser as well as Python\r
- setsid = getattr(os, 'setsid', None)\r
- if not setsid:\r
- setsid = getattr(os, 'setpgrp', None)\r
-\r
- try:\r
- p = subprocess.Popen(["kfmclient", action, url],\r
- close_fds=True, stdin=devnull,\r
- stdout=devnull, stderr=devnull)\r
- except OSError:\r
- # fall through to next variant\r
- pass\r
- else:\r
- p.wait()\r
- # kfmclient's return code unfortunately has no meaning as it seems\r
- return True\r
-\r
- try:\r
- p = subprocess.Popen(["konqueror", "--silent", url],\r
- close_fds=True, stdin=devnull,\r
- stdout=devnull, stderr=devnull,\r
- preexec_fn=setsid)\r
- except OSError:\r
- # fall through to next variant\r
- pass\r
- else:\r
- if p.poll() is None:\r
- # Should be running now.\r
- return True\r
-\r
- try:\r
- p = subprocess.Popen(["kfm", "-d", url],\r
- close_fds=True, stdin=devnull,\r
- stdout=devnull, stderr=devnull,\r
- preexec_fn=setsid)\r
- except OSError:\r
- return False\r
- else:\r
- return (p.poll() is None)\r
-\r
-\r
-class Grail(BaseBrowser):\r
- # There should be a way to maintain a connection to Grail, but the\r
- # Grail remote control protocol doesn't really allow that at this\r
- # point. It probably never will!\r
- def _find_grail_rc(self):\r
- import glob\r
- import pwd\r
- import socket\r
- import tempfile\r
- tempdir = os.path.join(tempfile.gettempdir(),\r
- ".grail-unix")\r
- user = pwd.getpwuid(os.getuid())[0]\r
- filename = os.path.join(tempdir, user + "-*")\r
- maybes = glob.glob(filename)\r
- if not maybes:\r
- return None\r
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\r
- for fn in maybes:\r
- # need to PING each one until we find one that's live\r
- try:\r
- s.connect(fn)\r
- except socket.error:\r
- # no good; attempt to clean it out, but don't fail:\r
- try:\r
- os.unlink(fn)\r
- except IOError:\r
- pass\r
- else:\r
- return s\r
-\r
- def _remote(self, action):\r
- s = self._find_grail_rc()\r
- if not s:\r
- return 0\r
- s.send(action)\r
- s.close()\r
- return 1\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- if new:\r
- ok = self._remote("LOADNEW " + url)\r
- else:\r
- ok = self._remote("LOAD " + url)\r
- return ok\r
-\r
-\r
-#\r
-# Platform support for Unix\r
-#\r
-\r
-# These are the right tests because all these Unix browsers require either\r
-# a console terminal or an X display to run.\r
-\r
-def register_X_browsers():\r
-\r
- # The default GNOME browser\r
- if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):\r
- register("gnome-open", None, BackgroundBrowser("gnome-open"))\r
-\r
- # The default KDE browser\r
- if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):\r
- register("kfmclient", Konqueror, Konqueror("kfmclient"))\r
-\r
- # The Mozilla/Netscape browsers\r
- for browser in ("mozilla-firefox", "firefox",\r
- "mozilla-firebird", "firebird",\r
- "seamonkey", "mozilla", "netscape"):\r
- if _iscommand(browser):\r
- register(browser, None, Mozilla(browser))\r
-\r
- # Konqueror/kfm, the KDE browser.\r
- if _iscommand("kfm"):\r
- register("kfm", Konqueror, Konqueror("kfm"))\r
- elif _iscommand("konqueror"):\r
- register("konqueror", Konqueror, Konqueror("konqueror"))\r
-\r
- # Gnome's Galeon and Epiphany\r
- for browser in ("galeon", "epiphany"):\r
- if _iscommand(browser):\r
- register(browser, None, Galeon(browser))\r
-\r
- # Skipstone, another Gtk/Mozilla based browser\r
- if _iscommand("skipstone"):\r
- register("skipstone", None, BackgroundBrowser("skipstone"))\r
-\r
- # Opera, quite popular\r
- if _iscommand("opera"):\r
- register("opera", None, Opera("opera"))\r
-\r
- # Next, Mosaic -- old but still in use.\r
- if _iscommand("mosaic"):\r
- register("mosaic", None, BackgroundBrowser("mosaic"))\r
-\r
- # Grail, the Python browser. Does anybody still use it?\r
- if _iscommand("grail"):\r
- register("grail", Grail, None)\r
-\r
-# Prefer X browsers if present\r
-if os.environ.get("DISPLAY"):\r
- register_X_browsers()\r
-\r
-# Also try console browsers\r
-if os.environ.get("TERM"):\r
- # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>\r
- if _iscommand("links"):\r
- register("links", None, GenericBrowser("links"))\r
- if _iscommand("elinks"):\r
- register("elinks", None, Elinks("elinks"))\r
- # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>\r
- if _iscommand("lynx"):\r
- register("lynx", None, GenericBrowser("lynx"))\r
- # The w3m browser <http://w3m.sourceforge.net/>\r
- if _iscommand("w3m"):\r
- register("w3m", None, GenericBrowser("w3m"))\r
-\r
-#\r
-# Platform support for Windows\r
-#\r
-\r
-if sys.platform[:3] == "win":\r
- class WindowsDefault(BaseBrowser):\r
- def open(self, url, new=0, autoraise=True):\r
- try:\r
- os.startfile(url)\r
- except WindowsError:\r
- # [Error 22] No application is associated with the specified\r
- # file for this operation: '<URL>'\r
- return False\r
- else:\r
- return True\r
-\r
- _tryorder = []\r
- _browsers = {}\r
-\r
- # First try to use the default Windows browser\r
- register("windows-default", WindowsDefault)\r
-\r
- # Detect some common Windows browsers, fallback to IE\r
- iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),\r
- "Internet Explorer\\IEXPLORE.EXE")\r
- for browser in ("firefox", "firebird", "seamonkey", "mozilla",\r
- "netscape", "opera", iexplore):\r
- if _iscommand(browser):\r
- register(browser, None, BackgroundBrowser(browser))\r
-\r
-#\r
-# Platform support for MacOS\r
-#\r
-\r
-if sys.platform == 'darwin':\r
- # Adapted from patch submitted to SourceForge by Steven J. Burr\r
- class MacOSX(BaseBrowser):\r
- """Launcher class for Aqua browsers on Mac OS X\r
-\r
- Optionally specify a browser name on instantiation. Note that this\r
- will not work for Aqua browsers if the user has moved the application\r
- package after installation.\r
-\r
- If no browser is specified, the default browser, as specified in the\r
- Internet System Preferences panel, will be used.\r
- """\r
- def __init__(self, name):\r
- self.name = name\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- assert "'" not in url\r
- # hack for local urls\r
- if not ':' in url:\r
- url = 'file:'+url\r
-\r
- # new must be 0 or 1\r
- new = int(bool(new))\r
- if self.name == "default":\r
- # User called open, open_new or get without a browser parameter\r
- script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser\r
- else:\r
- # User called get and chose a browser\r
- if self.name == "OmniWeb":\r
- toWindow = ""\r
- else:\r
- # Include toWindow parameter of OpenURL command for browsers\r
- # that support it. 0 == new window; -1 == existing\r
- toWindow = "toWindow %d" % (new - 1)\r
- cmd = 'OpenURL "%s"' % url.replace('"', '%22')\r
- script = '''tell application "%s"\r
- activate\r
- %s %s\r
- end tell''' % (self.name, cmd, toWindow)\r
- # Open pipe to AppleScript through osascript command\r
- osapipe = os.popen("osascript", "w")\r
- if osapipe is None:\r
- return False\r
- # Write script to osascript's stdin\r
- osapipe.write(script)\r
- rc = osapipe.close()\r
- return not rc\r
-\r
- class MacOSXOSAScript(BaseBrowser):\r
- def __init__(self, name):\r
- self._name = name\r
-\r
- def open(self, url, new=0, autoraise=True):\r
- if self._name == 'default':\r
- script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser\r
- else:\r
- script = '''\r
- tell application "%s"\r
- activate\r
- open location "%s"\r
- end\r
- '''%(self._name, url.replace('"', '%22'))\r
-\r
- osapipe = os.popen("osascript", "w")\r
- if osapipe is None:\r
- return False\r
-\r
- osapipe.write(script)\r
- rc = osapipe.close()\r
- return not rc\r
-\r
-\r
- # Don't clear _tryorder or _browsers since OS X can use above Unix support\r
- # (but we prefer using the OS X specific stuff)\r
- register("safari", None, MacOSXOSAScript('safari'), -1)\r
- register("firefox", None, MacOSXOSAScript('firefox'), -1)\r
- register("MacOSX", None, MacOSXOSAScript('default'), -1)\r
-\r
-\r
-#\r
-# Platform support for OS/2\r
-#\r
-\r
-if sys.platform[:3] == "os2" and _iscommand("netscape"):\r
- _tryorder = []\r
- _browsers = {}\r
- register("os2netscape", None,\r
- GenericBrowser(["start", "netscape", "%s"]), -1)\r
-\r
-\r
-# OK, now that we know what the default preference orders for each\r
-# platform are, allow user to override them with the BROWSER variable.\r
-if "BROWSER" in os.environ:\r
- _userchoices = os.environ["BROWSER"].split(os.pathsep)\r
- _userchoices.reverse()\r
-\r
- # Treat choices in same way as if passed into get() but do register\r
- # and prepend to _tryorder\r
- for cmdline in _userchoices:\r
- if cmdline != '':\r
- cmd = _synthesize(cmdline, -1)\r
- if cmd[1] is None:\r
- register(cmdline, None, GenericBrowser(cmdline), -1)\r
- cmdline = None # to make del work if _userchoices was empty\r
- del cmdline\r
- del _userchoices\r
-\r
-# what to do if _tryorder is now empty?\r
-\r
-\r
-def main():\r
- import getopt\r
- usage = """Usage: %s [-n | -t] url\r
- -n: open new window\r
- -t: open new tab""" % sys.argv[0]\r
- try:\r
- opts, args = getopt.getopt(sys.argv[1:], 'ntd')\r
- except getopt.error, msg:\r
- print >>sys.stderr, msg\r
- print >>sys.stderr, usage\r
- sys.exit(1)\r
- new_win = 0\r
- for o, a in opts:\r
- if o == '-n': new_win = 1\r
- elif o == '-t': new_win = 2\r
- if len(args) != 1:\r
- print >>sys.stderr, usage\r
- sys.exit(1)\r
-\r
- url = args[0]\r
- open(url, new_win)\r
-\r
- print "\a"\r
-\r
-if __name__ == "__main__":\r
- main()\r