source: python/vendor/Python-2.7.6/Lib/webbrowser.py

Last change on this file was 388, checked in by dmik, 11 years ago

python: Update vendor to 2.7.6.

  • Property svn:eol-style set to native
File size: 22.2 KB
Line 
1#! /usr/bin/env python
2"""Interfaces for launching and remotely controlling Web browsers."""
3# Maintained by Georg Brandl.
4
5import os
6import shlex
7import sys
8import stat
9import subprocess
10import time
11
12__all__ = ["Error", "open", "open_new", "open_new_tab", "get", "register"]
13
14class Error(Exception):
15 pass
16
17_browsers = {} # Dictionary of available browser controllers
18_tryorder = [] # Preference order of available browsers
19
20def register(name, klass, instance=None, update_tryorder=1):
21 """Register a browser connector and, optionally, connection."""
22 _browsers[name.lower()] = [klass, instance]
23 if update_tryorder > 0:
24 _tryorder.append(name)
25 elif update_tryorder < 0:
26 _tryorder.insert(0, name)
27
28def get(using=None):
29 """Return a browser launcher instance appropriate for the environment."""
30 if using is not None:
31 alternatives = [using]
32 else:
33 alternatives = _tryorder
34 for browser in alternatives:
35 if '%s' in browser:
36 # User gave us a command line, split it into name and args
37 browser = shlex.split(browser)
38 if browser[-1] == '&':
39 return BackgroundBrowser(browser[:-1])
40 else:
41 return GenericBrowser(browser)
42 else:
43 # User gave us a browser name or path.
44 try:
45 command = _browsers[browser.lower()]
46 except KeyError:
47 command = _synthesize(browser)
48 if command[1] is not None:
49 return command[1]
50 elif command[0] is not None:
51 return command[0]()
52 raise Error("could not locate runnable browser")
53
54# Please note: the following definition hides a builtin function.
55# It is recommended one does "import webbrowser" and uses webbrowser.open(url)
56# instead of "from webbrowser import *".
57
58def open(url, new=0, autoraise=True):
59 for name in _tryorder:
60 browser = get(name)
61 if browser.open(url, new, autoraise):
62 return True
63 return False
64
65def open_new(url):
66 return open(url, 1)
67
68def open_new_tab(url):
69 return open(url, 2)
70
71
72def _synthesize(browser, update_tryorder=1):
73 """Attempt to synthesize a controller base on existing controllers.
74
75 This is useful to create a controller when a user specifies a path to
76 an entry in the BROWSER environment variable -- we can copy a general
77 controller to operate using a specific installation of the desired
78 browser in this way.
79
80 If we can't create a controller in this way, or if there is no
81 executable for the requested browser, return [None, None].
82
83 """
84 cmd = browser.split()[0]
85 if not _iscommand(cmd):
86 return [None, None]
87 name = os.path.basename(cmd)
88 try:
89 command = _browsers[name.lower()]
90 except KeyError:
91 return [None, None]
92 # now attempt to clone to fit the new name:
93 controller = command[1]
94 if controller and name.lower() == controller.basename:
95 import copy
96 controller = copy.copy(controller)
97 controller.name = browser
98 controller.basename = os.path.basename(browser)
99 register(browser, None, controller, update_tryorder)
100 return [None, controller]
101 return [None, None]
102
103
104if sys.platform[:3] == "win":
105 def _isexecutable(cmd):
106 cmd = cmd.lower()
107 if os.path.isfile(cmd) and cmd.endswith((".exe", ".bat")):
108 return True
109 for ext in ".exe", ".bat":
110 if os.path.isfile(cmd + ext):
111 return True
112 return False
113else:
114 def _isexecutable(cmd):
115 if os.path.isfile(cmd):
116 mode = os.stat(cmd)[stat.ST_MODE]
117 if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
118 return True
119 return False
120
121def _iscommand(cmd):
122 """Return True if cmd is executable or can be found on the executable
123 search path."""
124 if _isexecutable(cmd):
125 return True
126 path = os.environ.get("PATH")
127 if not path:
128 return False
129 for d in path.split(os.pathsep):
130 exe = os.path.join(d, cmd)
131 if _isexecutable(exe):
132 return True
133 return False
134
135
136# General parent classes
137
138class BaseBrowser(object):
139 """Parent class for all browsers. Do not use directly."""
140
141 args = ['%s']
142
143 def __init__(self, name=""):
144 self.name = name
145 self.basename = name
146
147 def open(self, url, new=0, autoraise=True):
148 raise NotImplementedError
149
150 def open_new(self, url):
151 return self.open(url, 1)
152
153 def open_new_tab(self, url):
154 return self.open(url, 2)
155
156
157class GenericBrowser(BaseBrowser):
158 """Class for all browsers started with a command
159 and without remote functionality."""
160
161 def __init__(self, name):
162 if isinstance(name, basestring):
163 self.name = name
164 self.args = ["%s"]
165 else:
166 # name should be a list with arguments
167 self.name = name[0]
168 self.args = name[1:]
169 self.basename = os.path.basename(self.name)
170
171 def open(self, url, new=0, autoraise=True):
172 cmdline = [self.name] + [arg.replace("%s", url)
173 for arg in self.args]
174 try:
175 if sys.platform[:3] == 'win':
176 p = subprocess.Popen(cmdline)
177 else:
178 p = subprocess.Popen(cmdline, close_fds=True)
179 return not p.wait()
180 except OSError:
181 return False
182
183
184class BackgroundBrowser(GenericBrowser):
185 """Class for all browsers which are to be started in the
186 background."""
187
188 def open(self, url, new=0, autoraise=True):
189 cmdline = [self.name] + [arg.replace("%s", url)
190 for arg in self.args]
191 try:
192 if sys.platform[:3] == 'win':
193 p = subprocess.Popen(cmdline)
194 else:
195 setsid = getattr(os, 'setsid', None)
196 if not setsid:
197 setsid = getattr(os, 'setpgrp', None)
198 p = subprocess.Popen(cmdline, close_fds=True, preexec_fn=setsid)
199 return (p.poll() is None)
200 except OSError:
201 return False
202
203
204class UnixBrowser(BaseBrowser):
205 """Parent class for all Unix browsers with remote functionality."""
206
207 raise_opts = None
208 remote_args = ['%action', '%s']
209 remote_action = None
210 remote_action_newwin = None
211 remote_action_newtab = None
212 background = False
213 redirect_stdout = True
214
215 def _invoke(self, args, remote, autoraise):
216 raise_opt = []
217 if remote and self.raise_opts:
218 # use autoraise argument only for remote invocation
219 autoraise = int(autoraise)
220 opt = self.raise_opts[autoraise]
221 if opt: raise_opt = [opt]
222
223 cmdline = [self.name] + raise_opt + args
224
225 if remote or self.background:
226 inout = file(os.devnull, "r+")
227 else:
228 # for TTY browsers, we need stdin/out
229 inout = None
230 # if possible, put browser in separate process group, so
231 # keyboard interrupts don't affect browser as well as Python
232 setsid = getattr(os, 'setsid', None)
233 if not setsid:
234 setsid = getattr(os, 'setpgrp', None)
235
236 p = subprocess.Popen(cmdline, close_fds=True, stdin=inout,
237 stdout=(self.redirect_stdout and inout or None),
238 stderr=inout, preexec_fn=setsid)
239 if remote:
240 # wait five seconds. If the subprocess is not finished, the
241 # remote invocation has (hopefully) started a new instance.
242 time.sleep(1)
243 rc = p.poll()
244 if rc is None:
245 time.sleep(4)
246 rc = p.poll()
247 if rc is None:
248 return True
249 # if remote call failed, open() will try direct invocation
250 return not rc
251 elif self.background:
252 if p.poll() is None:
253 return True
254 else:
255 return False
256 else:
257 return not p.wait()
258
259 def open(self, url, new=0, autoraise=True):
260 if new == 0:
261 action = self.remote_action
262 elif new == 1:
263 action = self.remote_action_newwin
264 elif new == 2:
265 if self.remote_action_newtab is None:
266 action = self.remote_action_newwin
267 else:
268 action = self.remote_action_newtab
269 else:
270 raise Error("Bad 'new' parameter to open(); " +
271 "expected 0, 1, or 2, got %s" % new)
272
273 args = [arg.replace("%s", url).replace("%action", action)
274 for arg in self.remote_args]
275 success = self._invoke(args, True, autoraise)
276 if not success:
277 # remote invocation failed, try straight way
278 args = [arg.replace("%s", url) for arg in self.args]
279 return self._invoke(args, False, False)
280 else:
281 return True
282
283
284class Mozilla(UnixBrowser):
285 """Launcher class for Mozilla/Netscape browsers."""
286
287 raise_opts = ["-noraise", "-raise"]
288 remote_args = ['-remote', 'openURL(%s%action)']
289 remote_action = ""
290 remote_action_newwin = ",new-window"
291 remote_action_newtab = ",new-tab"
292 background = True
293
294Netscape = Mozilla
295
296
297class Galeon(UnixBrowser):
298 """Launcher class for Galeon/Epiphany browsers."""
299
300 raise_opts = ["-noraise", ""]
301 remote_args = ['%action', '%s']
302 remote_action = "-n"
303 remote_action_newwin = "-w"
304 background = True
305
306
307class Chrome(UnixBrowser):
308 "Launcher class for Google Chrome browser."
309
310 remote_args = ['%action', '%s']
311 remote_action = ""
312 remote_action_newwin = "--new-window"
313 remote_action_newtab = ""
314 background = True
315
316Chromium = Chrome
317
318
319class Opera(UnixBrowser):
320 "Launcher class for Opera browser."
321
322 raise_opts = ["-noraise", ""]
323 remote_args = ['-remote', 'openURL(%s%action)']
324 remote_action = ""
325 remote_action_newwin = ",new-window"
326 remote_action_newtab = ",new-page"
327 background = True
328
329
330class Elinks(UnixBrowser):
331 "Launcher class for Elinks browsers."
332
333 remote_args = ['-remote', 'openURL(%s%action)']
334 remote_action = ""
335 remote_action_newwin = ",new-window"
336 remote_action_newtab = ",new-tab"
337 background = False
338
339 # elinks doesn't like its stdout to be redirected -
340 # it uses redirected stdout as a signal to do -dump
341 redirect_stdout = False
342
343
344class Konqueror(BaseBrowser):
345 """Controller for the KDE File Manager (kfm, or Konqueror).
346
347 See the output of ``kfmclient --commands``
348 for more information on the Konqueror remote-control interface.
349 """
350
351 def open(self, url, new=0, autoraise=True):
352 # XXX Currently I know no way to prevent KFM from opening a new win.
353 if new == 2:
354 action = "newTab"
355 else:
356 action = "openURL"
357
358 devnull = file(os.devnull, "r+")
359 # if possible, put browser in separate process group, so
360 # keyboard interrupts don't affect browser as well as Python
361 setsid = getattr(os, 'setsid', None)
362 if not setsid:
363 setsid = getattr(os, 'setpgrp', None)
364
365 try:
366 p = subprocess.Popen(["kfmclient", action, url],
367 close_fds=True, stdin=devnull,
368 stdout=devnull, stderr=devnull)
369 except OSError:
370 # fall through to next variant
371 pass
372 else:
373 p.wait()
374 # kfmclient's return code unfortunately has no meaning as it seems
375 return True
376
377 try:
378 p = subprocess.Popen(["konqueror", "--silent", url],
379 close_fds=True, stdin=devnull,
380 stdout=devnull, stderr=devnull,
381 preexec_fn=setsid)
382 except OSError:
383 # fall through to next variant
384 pass
385 else:
386 if p.poll() is None:
387 # Should be running now.
388 return True
389
390 try:
391 p = subprocess.Popen(["kfm", "-d", url],
392 close_fds=True, stdin=devnull,
393 stdout=devnull, stderr=devnull,
394 preexec_fn=setsid)
395 except OSError:
396 return False
397 else:
398 return (p.poll() is None)
399
400
401class Grail(BaseBrowser):
402 # There should be a way to maintain a connection to Grail, but the
403 # Grail remote control protocol doesn't really allow that at this
404 # point. It probably never will!
405 def _find_grail_rc(self):
406 import glob
407 import pwd
408 import socket
409 import tempfile
410 tempdir = os.path.join(tempfile.gettempdir(),
411 ".grail-unix")
412 user = pwd.getpwuid(os.getuid())[0]
413 filename = os.path.join(tempdir, user + "-*")
414 maybes = glob.glob(filename)
415 if not maybes:
416 return None
417 s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
418 for fn in maybes:
419 # need to PING each one until we find one that's live
420 try:
421 s.connect(fn)
422 except socket.error:
423 # no good; attempt to clean it out, but don't fail:
424 try:
425 os.unlink(fn)
426 except IOError:
427 pass
428 else:
429 return s
430
431 def _remote(self, action):
432 s = self._find_grail_rc()
433 if not s:
434 return 0
435 s.send(action)
436 s.close()
437 return 1
438
439 def open(self, url, new=0, autoraise=True):
440 if new:
441 ok = self._remote("LOADNEW " + url)
442 else:
443 ok = self._remote("LOAD " + url)
444 return ok
445
446
447#
448# Platform support for Unix
449#
450
451# These are the right tests because all these Unix browsers require either
452# a console terminal or an X display to run.
453
454def register_X_browsers():
455
456 # use xdg-open if around
457 if _iscommand("xdg-open"):
458 register("xdg-open", None, BackgroundBrowser("xdg-open"))
459
460 # The default GNOME3 browser
461 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gvfs-open"):
462 register("gvfs-open", None, BackgroundBrowser("gvfs-open"))
463
464 # The default GNOME browser
465 if "GNOME_DESKTOP_SESSION_ID" in os.environ and _iscommand("gnome-open"):
466 register("gnome-open", None, BackgroundBrowser("gnome-open"))
467
468 # The default KDE browser
469 if "KDE_FULL_SESSION" in os.environ and _iscommand("kfmclient"):
470 register("kfmclient", Konqueror, Konqueror("kfmclient"))
471
472 if _iscommand("x-www-browser"):
473 register("x-www-browser", None, BackgroundBrowser("x-www-browser"))
474
475 # The Mozilla/Netscape browsers
476 for browser in ("mozilla-firefox", "firefox",
477 "mozilla-firebird", "firebird",
478 "iceweasel", "iceape",
479 "seamonkey", "mozilla", "netscape"):
480 if _iscommand(browser):
481 register(browser, None, Mozilla(browser))
482
483 # Konqueror/kfm, the KDE browser.
484 if _iscommand("kfm"):
485 register("kfm", Konqueror, Konqueror("kfm"))
486 elif _iscommand("konqueror"):
487 register("konqueror", Konqueror, Konqueror("konqueror"))
488
489 # Gnome's Galeon and Epiphany
490 for browser in ("galeon", "epiphany"):
491 if _iscommand(browser):
492 register(browser, None, Galeon(browser))
493
494 # Skipstone, another Gtk/Mozilla based browser
495 if _iscommand("skipstone"):
496 register("skipstone", None, BackgroundBrowser("skipstone"))
497
498 # Google Chrome/Chromium browsers
499 for browser in ("google-chrome", "chrome", "chromium", "chromium-browser"):
500 if _iscommand(browser):
501 register(browser, None, Chrome(browser))
502
503 # Opera, quite popular
504 if _iscommand("opera"):
505 register("opera", None, Opera("opera"))
506
507 # Next, Mosaic -- old but still in use.
508 if _iscommand("mosaic"):
509 register("mosaic", None, BackgroundBrowser("mosaic"))
510
511 # Grail, the Python browser. Does anybody still use it?
512 if _iscommand("grail"):
513 register("grail", Grail, None)
514
515# Prefer X browsers if present
516if os.environ.get("DISPLAY"):
517 register_X_browsers()
518
519# Also try console browsers
520if os.environ.get("TERM"):
521 if _iscommand("www-browser"):
522 register("www-browser", None, GenericBrowser("www-browser"))
523 # The Links/elinks browsers <http://artax.karlin.mff.cuni.cz/~mikulas/links/>
524 if _iscommand("links"):
525 register("links", None, GenericBrowser("links"))
526 if _iscommand("elinks"):
527 register("elinks", None, Elinks("elinks"))
528 # The Lynx browser <http://lynx.isc.org/>, <http://lynx.browser.org/>
529 if _iscommand("lynx"):
530 register("lynx", None, GenericBrowser("lynx"))
531 # The w3m browser <http://w3m.sourceforge.net/>
532 if _iscommand("w3m"):
533 register("w3m", None, GenericBrowser("w3m"))
534
535#
536# Platform support for Windows
537#
538
539if sys.platform[:3] == "win":
540 class WindowsDefault(BaseBrowser):
541 def open(self, url, new=0, autoraise=True):
542 try:
543 os.startfile(url)
544 except WindowsError:
545 # [Error 22] No application is associated with the specified
546 # file for this operation: '<URL>'
547 return False
548 else:
549 return True
550
551 _tryorder = []
552 _browsers = {}
553
554 # First try to use the default Windows browser
555 register("windows-default", WindowsDefault)
556
557 # Detect some common Windows browsers, fallback to IE
558 iexplore = os.path.join(os.environ.get("PROGRAMFILES", "C:\\Program Files"),
559 "Internet Explorer\\IEXPLORE.EXE")
560 for browser in ("firefox", "firebird", "seamonkey", "mozilla",
561 "netscape", "opera", iexplore):
562 if _iscommand(browser):
563 register(browser, None, BackgroundBrowser(browser))
564
565#
566# Platform support for MacOS
567#
568
569if sys.platform == 'darwin':
570 # Adapted from patch submitted to SourceForge by Steven J. Burr
571 class MacOSX(BaseBrowser):
572 """Launcher class for Aqua browsers on Mac OS X
573
574 Optionally specify a browser name on instantiation. Note that this
575 will not work for Aqua browsers if the user has moved the application
576 package after installation.
577
578 If no browser is specified, the default browser, as specified in the
579 Internet System Preferences panel, will be used.
580 """
581 def __init__(self, name):
582 self.name = name
583
584 def open(self, url, new=0, autoraise=True):
585 assert "'" not in url
586 # hack for local urls
587 if not ':' in url:
588 url = 'file:'+url
589
590 # new must be 0 or 1
591 new = int(bool(new))
592 if self.name == "default":
593 # User called open, open_new or get without a browser parameter
594 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
595 else:
596 # User called get and chose a browser
597 if self.name == "OmniWeb":
598 toWindow = ""
599 else:
600 # Include toWindow parameter of OpenURL command for browsers
601 # that support it. 0 == new window; -1 == existing
602 toWindow = "toWindow %d" % (new - 1)
603 cmd = 'OpenURL "%s"' % url.replace('"', '%22')
604 script = '''tell application "%s"
605 activate
606 %s %s
607 end tell''' % (self.name, cmd, toWindow)
608 # Open pipe to AppleScript through osascript command
609 osapipe = os.popen("osascript", "w")
610 if osapipe is None:
611 return False
612 # Write script to osascript's stdin
613 osapipe.write(script)
614 rc = osapipe.close()
615 return not rc
616
617 class MacOSXOSAScript(BaseBrowser):
618 def __init__(self, name):
619 self._name = name
620
621 def open(self, url, new=0, autoraise=True):
622 if self._name == 'default':
623 script = 'open location "%s"' % url.replace('"', '%22') # opens in default browser
624 else:
625 script = '''
626 tell application "%s"
627 activate
628 open location "%s"
629 end
630 '''%(self._name, url.replace('"', '%22'))
631
632 osapipe = os.popen("osascript", "w")
633 if osapipe is None:
634 return False
635
636 osapipe.write(script)
637 rc = osapipe.close()
638 return not rc
639
640
641 # Don't clear _tryorder or _browsers since OS X can use above Unix support
642 # (but we prefer using the OS X specific stuff)
643 register("safari", None, MacOSXOSAScript('safari'), -1)
644 register("firefox", None, MacOSXOSAScript('firefox'), -1)
645 register("MacOSX", None, MacOSXOSAScript('default'), -1)
646
647
648#
649# Platform support for OS/2
650#
651
652if sys.platform[:3] == "os2" and _iscommand("netscape"):
653 _tryorder = []
654 _browsers = {}
655 register("os2netscape", None,
656 GenericBrowser(["start", "netscape", "%s"]), -1)
657
658
659# OK, now that we know what the default preference orders for each
660# platform are, allow user to override them with the BROWSER variable.
661if "BROWSER" in os.environ:
662 _userchoices = os.environ["BROWSER"].split(os.pathsep)
663 _userchoices.reverse()
664
665 # Treat choices in same way as if passed into get() but do register
666 # and prepend to _tryorder
667 for cmdline in _userchoices:
668 if cmdline != '':
669 cmd = _synthesize(cmdline, -1)
670 if cmd[1] is None:
671 register(cmdline, None, GenericBrowser(cmdline), -1)
672 cmdline = None # to make del work if _userchoices was empty
673 del cmdline
674 del _userchoices
675
676# what to do if _tryorder is now empty?
677
678
679def main():
680 import getopt
681 usage = """Usage: %s [-n | -t] url
682 -n: open new window
683 -t: open new tab""" % sys.argv[0]
684 try:
685 opts, args = getopt.getopt(sys.argv[1:], 'ntd')
686 except getopt.error, msg:
687 print >>sys.stderr, msg
688 print >>sys.stderr, usage
689 sys.exit(1)
690 new_win = 0
691 for o, a in opts:
692 if o == '-n': new_win = 1
693 elif o == '-t': new_win = 2
694 if len(args) != 1:
695 print >>sys.stderr, usage
696 sys.exit(1)
697
698 url = args[0]
699 open(url, new_win)
700
701 print "\a"
702
703if __name__ == "__main__":
704 main()
Note: See TracBrowser for help on using the repository browser.