blob: e8fbe2d5a84eed41f6dc98c746181db31dc5700e [file] [log] [blame]
Daniel Dunbar6fd6bd02008-09-22 18:44:46 +00001"""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)