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