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