1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4"""Utility for opening a file using the default application in a cross-platform 5manner. Modified from http://code.activestate.com/recipes/511443/. 6""" 7 8__version__ = "1.1x" 9__all__ = ["open"] 10 11import os 12import sys 13import webbrowser 14import subprocess 15 16_controllers = {} 17_open = None 18 19 20class BaseController(object): 21 """Base class for open program controllers.""" 22 23 def __init__(self, name): 24 self.name = name 25 26 def open(self, filename): 27 raise NotImplementedError 28 29 30class Controller(BaseController): 31 """Controller for a generic open program.""" 32 33 def __init__(self, *args): 34 super(Controller, self).__init__(os.path.basename(args[0])) 35 self.args = list(args) 36 37 def _invoke(self, cmdline): 38 if sys.platform[:3] == "win": 39 closefds = False 40 startupinfo = subprocess.STARTUPINFO() 41 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 42 else: 43 closefds = True 44 startupinfo = None 45 46 if ( 47 os.environ.get("DISPLAY") 48 or sys.platform[:3] == "win" 49 or sys.platform == "darwin" 50 ): 51 inout = file(os.devnull, "r+") 52 else: 53 # for TTY programs, we need stdin/out 54 inout = None 55 56 # if possible, put the child precess in separate process group, 57 # so keyboard interrupts don't affect child precess as well as 58 # Python 59 setsid = getattr(os, "setsid", None) 60 if not setsid: 61 setsid = getattr(os, "setpgrp", None) 62 63 pipe = subprocess.Popen( 64 cmdline, 65 stdin=inout, 66 stdout=inout, 67 stderr=inout, 68 close_fds=closefds, 69 preexec_fn=setsid, 70 startupinfo=startupinfo, 71 ) 72 73 # It is assumed that this kind of tools (gnome-open, kfmclient, 74 # exo-open, xdg-open and open for OSX) immediately exit after launching 75 # the specific application 76 returncode = pipe.wait() 77 if hasattr(self, "fixreturncode"): 78 returncode = self.fixreturncode(returncode) 79 return not returncode 80 81 def open(self, filename): 82 if isinstance(filename, basestring): 83 cmdline = self.args + [filename] 84 else: 85 # assume it is a sequence 86 cmdline = self.args + filename 87 try: 88 return self._invoke(cmdline) 89 except OSError: 90 return False 91 92 93# Platform support for Windows 94if sys.platform[:3] == "win": 95 96 class Start(BaseController): 97 """Controller for the win32 start program through os.startfile.""" 98 99 def open(self, filename): 100 try: 101 os.startfile(filename) 102 except WindowsError: 103 # [Error 22] No application is associated with the specified 104 # file for this operation: '<URL>' 105 return False 106 else: 107 return True 108 109 _controllers["windows-default"] = Start("start") 110 _open = _controllers["windows-default"].open 111 112 113# Platform support for MacOS 114elif sys.platform == "darwin": 115 _controllers["open"] = Controller("open") 116 _open = _controllers["open"].open 117 118 119# Platform support for Unix 120else: 121 122 try: 123 from commands import getoutput 124 except ImportError: 125 from subprocess import getoutput 126 127 # @WARNING: use the private API of the webbrowser module 128 from webbrowser import _iscommand 129 130 class KfmClient(Controller): 131 """Controller for the KDE kfmclient program.""" 132 133 def __init__(self, kfmclient="kfmclient"): 134 super(KfmClient, self).__init__(kfmclient, "exec") 135 self.kde_version = self.detect_kde_version() 136 137 def detect_kde_version(self): 138 kde_version = None 139 try: 140 info = getoutput("kde-config --version") 141 142 for line in info.splitlines(): 143 if line.startswith("KDE"): 144 kde_version = line.split(":")[-1].strip() 145 break 146 except (OSError, RuntimeError): 147 pass 148 149 return kde_version 150 151 def fixreturncode(self, returncode): 152 if returncode is not None and self.kde_version > "3.5.4": 153 return returncode 154 else: 155 return os.EX_OK 156 157 def detect_desktop_environment(): 158 """Checks for known desktop environments 159 160 Return the desktop environments name, lowercase (kde, gnome, xfce) 161 or "generic" 162 163 """ 164 165 desktop_environment = "generic" 166 167 if os.environ.get("KDE_FULL_SESSION") == "true": 168 desktop_environment = "kde" 169 elif os.environ.get("GNOME_DESKTOP_SESSION_ID"): 170 desktop_environment = "gnome" 171 else: 172 try: 173 info = getoutput("xprop -root _DT_SAVE_MODE") 174 if ' = "xfce4"' in info: 175 desktop_environment = "xfce" 176 except (OSError, RuntimeError): 177 pass 178 179 return desktop_environment 180 181 def register_X_controllers(): 182 if _iscommand("kfmclient"): 183 _controllers["kde-open"] = KfmClient() 184 185 for command in ("gnome-open", "exo-open", "xdg-open"): 186 if _iscommand(command): 187 _controllers[command] = Controller(command) 188 189 def get(): 190 controllers_map = { 191 "gnome": "gnome-open", 192 "kde": "kde-open", 193 "xfce": "exo-open", 194 } 195 196 desktop_environment = detect_desktop_environment() 197 198 try: 199 controller_name = controllers_map[desktop_environment] 200 return _controllers[controller_name].open 201 202 except KeyError: 203 if "xdg-open" in _controllers: 204 return _controllers["xdg-open"].open 205 else: 206 return webbrowser.open 207 208 if os.environ.get("DISPLAY"): 209 register_X_controllers() 210 _open = get() 211 212 213def open(filename): 214 """Open a file or a URL in the registered default application.""" 215 216 return _open(filename) 217