Kaydet (Commit) 23929f28 authored tarafından Georg Brandl's avatar Georg Brandl

Try to resolve the remaining webbrowser issues (backgrounding, local urls)

üst 777f1083
...@@ -19,9 +19,9 @@ process will block until the user exits the browser. ...@@ -19,9 +19,9 @@ process will block until the user exits the browser.
If the environment variable \envvar{BROWSER} exists, it If the environment variable \envvar{BROWSER} exists, it
is interpreted to override the platform default list of browsers, as a is interpreted to override the platform default list of browsers, as a
os.pathsep-separated list of browsers to try in order. When the value of os.pathsep-separated list of browsers to try in order. When the value of
a list part contains the string \code{\%s}, then it is interpreted as a list part contains the string \code{\%s}, then it is
a literal browser command line to be used with the argument URL interpreted as a literal browser command line to be used with the argument URL
substituted for the \code{\%s}; if the part does not contain substituted for \code{\%s}; if the part does not contain
\code{\%s}, it is simply interpreted as the name of the browser to \code{\%s}, it is simply interpreted as the name of the browser to
launch. launch.
...@@ -52,7 +52,7 @@ The following functions are defined: ...@@ -52,7 +52,7 @@ The following functions are defined:
a new browser page ("tab") is opened if possible. If \var{autoraise} is a new browser page ("tab") is opened if possible. If \var{autoraise} is
true, the window is raised if possible (note that under many window true, the window is raised if possible (note that under many window
managers this will occur regardless of the setting of this variable). managers this will occur regardless of the setting of this variable).
\versionchanged[\var{new} can now be 2]{2.5}
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{open_new}{url} \begin{funcdesc}{open_new}{url}
...@@ -96,16 +96,17 @@ in this module. ...@@ -96,16 +96,17 @@ in this module.
\lineiii{'netscape'}{\class{Mozilla('netscape')}}{} \lineiii{'netscape'}{\class{Mozilla('netscape')}}{}
\lineiii{'galeon'}{\class{Galeon('galeon')}}{} \lineiii{'galeon'}{\class{Galeon('galeon')}}{}
\lineiii{'epiphany'}{\class{Galeon('epiphany')}}{} \lineiii{'epiphany'}{\class{Galeon('epiphany')}}{}
\lineiii{'skipstone'}{\class{GenericBrowser('skipstone \%s \&')}}{} \lineiii{'skipstone'}{\class{BackgroundBrowser('skipstone')}}{}
\lineiii{'kfmclient'}{\class{Konqueror()}}{(1)}
\lineiii{'konqueror'}{\class{Konqueror()}}{(1)} \lineiii{'konqueror'}{\class{Konqueror()}}{(1)}
\lineiii{'kfm'}{\class{Konqueror()}}{(1)} \lineiii{'kfm'}{\class{Konqueror()}}{(1)}
\lineiii{'mosaic'}{\class{GenericBrowser('mosaic \%s \&')}}{} \lineiii{'mosaic'}{\class{BackgroundBrowser('mosaic')}}{}
\lineiii{'opera'}{\class{Opera()}}{} \lineiii{'opera'}{\class{Opera()}}{}
\lineiii{'grail'}{\class{Grail()}}{} \lineiii{'grail'}{\class{Grail()}}{}
\lineiii{'links'}{\class{GenericBrowser('links \%s')}}{} \lineiii{'links'}{\class{GenericBrowser('links')}}{}
\lineiii{'elinks'}{\class{Elinks('elinks')}}{} \lineiii{'elinks'}{\class{Elinks('elinks')}}{}
\lineiii{'lynx'}{\class{GenericBrowser('lynx \%s')}}{} \lineiii{'lynx'}{\class{GenericBrowser('lynx')}}{}
\lineiii{'w3m'}{\class{GenericBrowser('w3m \%s')}}{} \lineiii{'w3m'}{\class{GenericBrowser('w3m')}}{}
\lineiii{'windows-default'}{\class{WindowsDefault}}{(2)} \lineiii{'windows-default'}{\class{WindowsDefault}}{(2)}
\lineiii{'internet-config'}{\class{InternetConfig}}{(3)} \lineiii{'internet-config'}{\class{InternetConfig}}{(3)}
\lineiii{'macosx'}{\class{MacOSX('default')}}{(4)} \lineiii{'macosx'}{\class{MacOSX('default')}}{(4)}
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
import os import os
import sys import sys
import stat import stat
import subprocess
import time
__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"] __all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
...@@ -29,8 +31,8 @@ def get(using=None): ...@@ -29,8 +31,8 @@ def get(using=None):
alternatives = _tryorder alternatives = _tryorder
for browser in alternatives: for browser in alternatives:
if '%s' in browser: if '%s' in browser:
# User gave us a command line, don't mess with it. # User gave us a command line, split it into name and args
return GenericBrowser(browser) return GenericBrowser(browser.split())
else: else:
# User gave us a browser name or path. # User gave us a browser name or path.
try: try:
...@@ -129,8 +131,10 @@ def _iscommand(cmd): ...@@ -129,8 +131,10 @@ def _iscommand(cmd):
# General parent classes # General parent classes
class BaseBrowser(object): class BaseBrowser(object):
"""Parent class for all browsers.""" """Parent class for all browsers. Do not use directly."""
args = ['%s']
def __init__(self, name=""): def __init__(self, name=""):
self.name = name self.name = name
self.basename = name self.basename = name
...@@ -149,46 +153,98 @@ class GenericBrowser(BaseBrowser): ...@@ -149,46 +153,98 @@ class GenericBrowser(BaseBrowser):
"""Class for all browsers started with a command """Class for all browsers started with a command
and without remote functionality.""" and without remote functionality."""
def __init__(self, cmd): def __init__(self, name):
self.name, self.args = cmd.split(None, 1) if isinstance(name, basestring):
self.name = name
else:
# name should be a list with arguments
self.name = name[0]
self.args = name[1:]
self.basename = os.path.basename(self.name) self.basename = os.path.basename(self.name)
def open(self, url, new=0, autoraise=1): def open(self, url, new=0, autoraise=1):
assert "'" not in url cmdline = [self.name] + [arg.replace("%s", url)
command = "%s %s" % (self.name, self.args) for arg in self.args]
rc = os.system(command % url) try:
return not rc p = subprocess.Popen(cmdline, close_fds=True)
return not p.wait()
except OSError:
return False
class BackgroundBrowser(GenericBrowser):
"""Class for all browsers which are to be started in the
background."""
def open(self, url, new=0, autoraise=1):
cmdline = [self.name] + [arg.replace("%s", url)
for arg in self.args]
setsid = getattr(os, 'setsid', None)
if not setsid:
setsid = getattr(os, 'setpgrp', None)
try:
p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
return (p.poll() is None)
except OSError:
return False
class UnixBrowser(BaseBrowser): class UnixBrowser(BaseBrowser):
"""Parent class for all Unix browsers with remote functionality.""" """Parent class for all Unix browsers with remote functionality."""
raise_opts = None raise_opts = None
remote_args = ['%action', '%s']
remote_cmd = ''
remote_action = None remote_action = None
remote_action_newwin = None remote_action_newwin = None
remote_action_newtab = None remote_action_newtab = None
remote_background = False background = False
redirect_stdout = True
def _remote(self, url, action, autoraise):
autoraise = int(bool(autoraise)) # always 0/1 def _invoke(self, args, remote, autoraise):
raise_opt = self.raise_opts and self.raise_opts[autoraise] or '' raise_opt = []
cmd = "%s %s %s '%s' >/dev/null 2>&1" % (self.name, raise_opt, if remote and self.raise_opts:
self.remote_cmd, action) # use autoraise argument only for remote invocation
if self.remote_background: autoraise = int(bool(autoraise))
cmd += ' &' opt = self.raise_opts[autoraise]
rc = os.system(cmd) if opt: raise_opt = [opt]
if rc:
cmd = "%s %s" % (self.name, url) cmdline = [self.name] + raise_opt + args
if self.remote_background:
cmd += " &" if remote or self.background:
# bad return status, try again with simpler command inout = file(os.devnull, "r+")
rc = os.system(cmd) else:
return not rc # for TTY browsers, we need stdin/out
inout = None
# if possible, put browser in separate process group, so
# keyboard interrupts don't affect browser as well as Python
setsid = getattr(os, 'setsid', None)
if not setsid:
setsid = getattr(os, 'setpgrp', None)
p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
stdout=(self.redirect_stdout and inout or None),
stderr=inout, preexec_fn=setsid)
if remote:
# wait five secons. If the subprocess is not finished, the
# remote invocation has (hopefully) started a new instance.
time.sleep(1)
rc = p.poll()
if rc is None:
time.sleep(4)
rc = p.poll()
if rc is None:
return True
# if remote call failed, open() will try direct invocation
return not rc
elif self.background:
if p.poll() is None:
return True
else:
return False
else:
return not p.wait()
def open(self, url, new=0, autoraise=1): def open(self, url, new=0, autoraise=1):
assert "'" not in url
if new == 0: if new == 0:
action = self.remote_action action = self.remote_action
elif new == 1: elif new == 1:
...@@ -199,20 +255,31 @@ class UnixBrowser(BaseBrowser): ...@@ -199,20 +255,31 @@ class UnixBrowser(BaseBrowser):
else: else:
action = self.remote_action_newtab action = self.remote_action_newtab
else: else:
raise Error("Bad 'new' parameter to open(); expected 0, 1, or 2, got %s" % new) raise Error("Bad 'new' parameter to open(); " +
return self._remote(url, action % url, autoraise) "expected 0, 1, or 2, got %s" % new)
args = [arg.replace("%s", url).replace("%action", action)
for arg in self.remote_args]
success = self._invoke(args, True, autoraise)
if not success:
# remote invocation failed, try straight way
args = [arg.replace("%s", url) for arg in self.args]
return self._invoke(args, False, False)
else:
return True
class Mozilla(UnixBrowser): class Mozilla(UnixBrowser):
"""Launcher class for Mozilla/Netscape browsers.""" """Launcher class for Mozilla/Netscape browsers."""
raise_opts = ("-noraise", "-raise") raise_opts = ["-noraise", "-raise"]
remote_cmd = '-remote' remote_args = ['-remote', 'openURL(%s%action)']
remote_action = "openURL(%s)" remote_action = ""
remote_action_newwin = "openURL(%s,new-window)" remote_action_newwin = ",new-window"
remote_action_newtab = "openURL(%s,new-tab)" remote_action_newtab = ",new-tab"
remote_background = True
background = True
Netscape = Mozilla Netscape = Mozilla
...@@ -220,80 +287,101 @@ Netscape = Mozilla ...@@ -220,80 +287,101 @@ Netscape = Mozilla
class Galeon(UnixBrowser): class Galeon(UnixBrowser):
"""Launcher class for Galeon/Epiphany browsers.""" """Launcher class for Galeon/Epiphany browsers."""
raise_opts = ("-noraise", "") raise_opts = ["-noraise", ""]
remote_action = "-n '%s'" remote_args = ['%action', '%s']
remote_action_newwin = "-w '%s'" remote_action = "-n"
remote_action_newwin = "-w"
remote_background = True background = True
class Konqueror(BaseBrowser): class Opera(UnixBrowser):
"""Controller for the KDE File Manager (kfm, or Konqueror). "Launcher class for Opera browser."
See http://developer.kde.org/documentation/other/kfmclient.html
for more information on the Konqueror remote-control interface.
""" raise_opts = ["", "-raise"]
def _remote(self, url, action): remote_args = ['-remote', 'openURL(%s%action)']
# kfmclient is the new KDE way of opening URLs. remote_action = ""
cmd = "kfmclient %s >/dev/null 2>&1" % action remote_action_newwin = ",new-window"
rc = os.system(cmd) remote_action_newtab = ",new-page"
# Fall back to other variants. background = True
if rc:
if _iscommand("konqueror"):
rc = os.system(self.name + " --silent '%s' &" % url)
elif _iscommand("kfm"):
rc = os.system(self.name + " -d '%s' &" % url)
return not rc
def open(self, url, new=0, autoraise=1):
# XXX Currently I know no way to prevent KFM from
# opening a new win.
assert "'" not in url
if new == 2:
action = "newTab '%s'" % url
else:
action = "openURL '%s'" % url
ok = self._remote(url, action)
return ok
class Elinks(UnixBrowser):
"Launcher class for Elinks browsers."
class Opera(UnixBrowser): remote_args = ['-remote', 'openURL(%s%action)']
"Launcher class for Opera browser." remote_action = ""
remote_action_newwin = ",new-window"
remote_action_newtab = ",new-tab"
background = False
raise_opts = ("", "-raise") # elinks doesn't like its stdout to be redirected -
# it uses redirected stdout as a signal to do -dump
redirect_stdout = False
remote_cmd = '-remote'
remote_action = "openURL(%s)"
remote_action_newwin = "openURL(%s,new-window)"
remote_action_newtab = "openURL(%s,new-page)"
remote_background = True
class Konqueror(BaseBrowser):
"""Controller for the KDE File Manager (kfm, or Konqueror).
class Elinks(UnixBrowser): See the output of ``kfmclient --commands``
"Launcher class for Elinks browsers." for more information on the Konqueror remote-control interface.
"""
remote_cmd = '-remote' def open(self, url, new=0, autoraise=1):
remote_action = "openURL(%s)" # XXX Currently I know no way to prevent KFM from opening a new win.
remote_action_newwin = "openURL(%s,new-window)" if new == 2:
remote_action_newtab = "openURL(%s,new-tab)" action = "newTab"
else:
action = "openURL"
devnull = file(os.devnull, "r+")
# if possible, put browser in separate process group, so
# keyboard interrupts don't affect browser as well as Python
setsid = getattr(os, 'setsid', None)
if not setsid:
setsid = getattr(os, 'setpgrp', None)
try:
p = subprocess.Popen(["kfmclient", action, url],
close_fds=True, stdin=devnull,
stdout=devnull, stderr=devnull)
except OSError:
# fall through to next variant
pass
else:
p.wait()
# kfmclient's return code unfortunately has no meaning as it seems
return True
def _remote(self, url, action, autoraise): try:
# elinks doesn't like its stdout to be redirected - p = subprocess.Popen(["konqueror", "--silent", url],
# it uses redirected stdout as a signal to do -dump close_fds=True, stdin=devnull,
cmd = "%s %s '%s' 2>/dev/null" % (self.name, stdout=devnull, stderr=devnull,
self.remote_cmd, action) preexec_fn=setsid)
rc = os.system(cmd) except OSError:
if rc: # fall through to next variant
rc = os.system("%s %s" % (self.name, url)) pass
return not rc else:
if p.poll() is None:
# Should be running now.
return True
try:
p = subprocess.Popen(["kfm", "-d", url],
close_fds=True, stdin=devnull,
stdout=devnull, stderr=devnull,
preexec_fn=setsid)
except OSError:
return False
else:
return (p.poll() is None)
class Grail(BaseBrowser): class Grail(BaseBrowser):
# There should be a way to maintain a connection to Grail, but the # There should be a way to maintain a connection to Grail, but the
# Grail remote control protocol doesn't really allow that at this # Grail remote control protocol doesn't really allow that at this
# point. It probably neverwill! # point. It probably never will!
def _find_grail_rc(self): def _find_grail_rc(self):
import glob import glob
import pwd import pwd
...@@ -354,10 +442,9 @@ def register_X_browsers(): ...@@ -354,10 +442,9 @@ def register_X_browsers():
# if successful, register it # if successful, register it
if retncode == None and len(commd) != 0: if retncode == None and len(commd) != 0:
register("gnome", None, GenericBrowser( register("gnome", None, BackgroundBrowser(commd))
commd + " '%s' >/dev/null &"))
# First, the Mozilla/Netscape browsers # First, the Mozilla/Netscape browsers
for browser in ("mozilla-firefox", "firefox", for browser in ("mozilla-firefox", "firefox",
"mozilla-firebird", "firebird", "mozilla-firebird", "firebird",
"mozilla", "netscape"): "mozilla", "netscape"):
...@@ -377,7 +464,7 @@ def register_X_browsers(): ...@@ -377,7 +464,7 @@ def register_X_browsers():
# Skipstone, another Gtk/Mozilla based browser # Skipstone, another Gtk/Mozilla based browser
if _iscommand("skipstone"): if _iscommand("skipstone"):
register("skipstone", None, GenericBrowser("skipstone '%s' &")) register("skipstone", None, BackgroundBrowser("skipstone"))
# Opera, quite popular # Opera, quite popular
if _iscommand("opera"): if _iscommand("opera"):
...@@ -385,7 +472,7 @@ def register_X_browsers(): ...@@ -385,7 +472,7 @@ def register_X_browsers():
# Next, Mosaic -- old but still in use. # Next, Mosaic -- old but still in use.
if _iscommand("mosaic"): if _iscommand("mosaic"):
register("mosaic", None, GenericBrowser("mosaic '%s' &")) register("mosaic", None, BackgroundBrowser("mosaic"))
# Grail, the Python browser. Does anybody still use it? # Grail, the Python browser. Does anybody still use it?
if _iscommand("grail"): if _iscommand("grail"):
...@@ -399,15 +486,15 @@ if os.environ.get("DISPLAY"): ...@@ -399,15 +486,15 @@ if os.environ.get("DISPLAY"):
if os.environ.get("TERM"): if os.environ.get("TERM"):
# The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/> # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
if _iscommand("links"): if _iscommand("links"):
register("links", None, GenericBrowser("links '%s'")) register("links", None, GenericBrowser("links"))
if _iscommand("elinks"): if _iscommand("elinks"):
register("elinks", None, Elinks("elinks")) register("elinks", None, Elinks("elinks"))
# The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/> # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
if _iscommand("lynx"): if _iscommand("lynx"):
register("lynx", None, GenericBrowser("lynx '%s'")) register("lynx", None, GenericBrowser("lynx"))
# The w3m browser <http://w3m.sourceforge.net/> # The w3m browser <http://w3m.sourceforge.net/>
if _iscommand("w3m"): if _iscommand("w3m"):
register("w3m", None, GenericBrowser("w3m '%s'")) register("w3m", None, GenericBrowser("w3m"))
# #
# Platform support for Windows # Platform support for Windows
...@@ -424,7 +511,7 @@ if sys.platform[:3] == "win": ...@@ -424,7 +511,7 @@ if sys.platform[:3] == "win":
# Prefer mozilla/netscape/opera if present # Prefer mozilla/netscape/opera if present
for browser in ("firefox", "firebird", "mozilla", "netscape", "opera"): for browser in ("firefox", "firebird", "mozilla", "netscape", "opera"):
if _iscommand(browser): if _iscommand(browser):
register(browser, None, GenericBrowser(browser + ' %s')) register(browser, None, BackgroundBrowser(browser))
register("windows-default", WindowsDefault) register("windows-default", WindowsDefault)
# #
...@@ -460,6 +547,10 @@ if sys.platform == 'darwin': ...@@ -460,6 +547,10 @@ if sys.platform == 'darwin':
def open(self, url, new=0, autoraise=1): def open(self, url, new=0, autoraise=1):
assert "'" not in url assert "'" not in url
# hack for local urls
if not ':' in url:
url = 'file:'+url
# new must be 0 or 1 # new must be 0 or 1
new = int(bool(new)) new = int(bool(new))
if self.name == "default": if self.name == "default":
...@@ -500,7 +591,7 @@ if sys.platform[:3] == "os2" and _iscommand("netscape"): ...@@ -500,7 +591,7 @@ if sys.platform[:3] == "os2" and _iscommand("netscape"):
_tryorder = [] _tryorder = []
_browsers = {} _browsers = {}
register("os2netscape", None, register("os2netscape", None,
GenericBrowser("start netscape %s"), -1) GenericBrowser(["start", "netscape", "%s"]), -1)
# OK, now that we know what the default preference orders for each # OK, now that we know what the default preference orders for each
...@@ -543,5 +634,7 @@ def main(): ...@@ -543,5 +634,7 @@ def main():
url = args[0] url = args[0]
open(url, new_win) open(url, new_win)
print "\a"
if __name__ == "__main__": if __name__ == "__main__":
main() main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment