1 #!/usr/bin/env python 2 3 """ 4 Simple desktop integration for Python. This module provides desktop environment 5 detection and resource opening support for a selection of common and 6 standardised desktop environments. 7 8 Copyright (C) 2005, 2006, 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2.1 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 23 24 -------- 25 26 Desktop Detection 27 ----------------- 28 29 To detect a specific desktop environment, use the get_desktop function. 30 To detect whether the desktop environment is standardised (according to the 31 proposed DESKTOP_LAUNCH standard), use the is_standard function. 32 33 Opening URLs 34 ------------ 35 36 To open a URL in the current desktop environment, relying on the automatic 37 detection of that environment, use the desktop.open function as follows: 38 39 desktop.open("http://www.python.org") 40 41 To override the detected desktop, specify the desktop parameter to the open 42 function as follows: 43 44 desktop.open("http://www.python.org", "KDE") # Insists on KDE 45 desktop.open("http://www.python.org", "GNOME") # Insists on GNOME 46 47 Without overriding using the desktop parameter, the open function will attempt 48 to use the "standard" desktop opening mechanism which is controlled by the 49 DESKTOP_LAUNCH environment variable as described below. 50 51 The DESKTOP_LAUNCH Environment Variable 52 --------------------------------------- 53 54 The DESKTOP_LAUNCH environment variable must be shell-quoted where appropriate, 55 as shown in some of the following examples: 56 57 DESKTOP_LAUNCH="kdialog --msgbox" Should present any opened URLs in 58 their entirety in a KDE message box. 59 (Command "kdialog" plus parameter.) 60 DESKTOP_LAUNCH="my\ opener" Should run the "my opener" program to 61 open URLs. 62 (Command "my opener", no parameters.) 63 DESKTOP_LAUNCH="my\ opener --url" Should run the "my opener" program to 64 open URLs. 65 (Command "my opener" plus parameter.) 66 67 Details of the DESKTOP_LAUNCH environment variable convention can be found here: 68 http://lists.freedesktop.org/archives/xdg/2004-August/004489.html 69 """ 70 71 __version__ = "0.3" 72 73 import os 74 import sys 75 76 # Provide suitable process creation functions. 77 78 try: 79 import subprocess 80 def _run(cmd, shell, wait): 81 opener = subprocess.Popen(cmd, shell=shell) 82 if wait: opener.wait() 83 return opener.pid 84 85 def _readfrom(cmd, shell): 86 opener = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 87 opener.stdin.close() 88 return opener.stdout.read() 89 90 def _status(cmd, shell): 91 opener = subprocess.Popen(cmd, shell=shell) 92 opener.wait() 93 return opener.returncode == 0 94 95 except ImportError: 96 import popen2 97 def _run(cmd, shell, wait): 98 opener = popen2.Popen3(cmd) 99 if wait: opener.wait() 100 return opener.pid 101 102 def _readfrom(cmd, shell): 103 opener = popen2.Popen3(cmd) 104 opener.tochild.close() 105 opener.childerr.close() 106 return opener.fromchild.read() 107 108 def _status(cmd, shell): 109 opener = popen2.Popen3(cmd) 110 opener.wait() 111 return opener.poll() == 0 112 113 import commands 114 115 # Private functions. 116 117 def _get_x11_vars(): 118 119 "Return suitable environment definitions for X11." 120 121 if not os.environ.get("DISPLAY", "").strip(): 122 return "DISPLAY=:0.0 " 123 else: 124 return "" 125 126 def _is_xfce(): 127 128 "Return whether XFCE is in use." 129 130 # XFCE detection involves testing the output of a program. 131 132 try: 133 return _readfrom(_get_x11_vars() + "xprop -root _DT_SAVE_MODE", shell=1).strip().endswith(' = "xfce4"') 134 except OSError: 135 return 0 136 137 def _is_x11(): 138 139 "Return whether the X Window System is in use." 140 141 return os.environ.has_key("DISPLAY") 142 143 # Introspection functions. 144 145 def get_desktop(): 146 147 """ 148 Detect the current desktop environment, returning the name of the 149 environment. If no environment could be detected, None is returned. 150 """ 151 152 if os.environ.has_key("KDE_FULL_SESSION") or \ 153 os.environ.has_key("KDE_MULTIHEAD"): 154 return "KDE" 155 elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \ 156 os.environ.has_key("GNOME_KEYRING_SOCKET"): 157 return "GNOME" 158 elif sys.platform == "darwin": 159 return "Mac OS X" 160 elif hasattr(os, "startfile"): 161 return "Windows" 162 elif _is_xfce(): 163 return "XFCE" 164 165 # KDE, GNOME and XFCE run on X11, so we have to test for X11 last. 166 167 if _is_x11(): 168 return "X11" 169 else: 170 return None 171 172 def use_desktop(desktop): 173 174 """ 175 Decide which desktop should be used, based on the detected desktop and a 176 supplied 'desktop' argument (which may be None). Return an identifier 177 indicating the desktop type as being either "standard" or one of the results 178 from the 'get_desktop' function. 179 """ 180 181 # Attempt to detect a desktop environment. 182 183 detected = get_desktop() 184 185 # Start with desktops whose existence can be easily tested. 186 187 if (desktop is None or desktop == "standard") and is_standard(): 188 return "standard" 189 elif (desktop is None or desktop == "Windows") and detected == "Windows": 190 return "Windows" 191 192 # Test for desktops where the overriding is not verified. 193 194 elif (desktop or detected) == "KDE": 195 return "KDE" 196 elif (desktop or detected) == "GNOME": 197 return "GNOME" 198 elif (desktop or detected) == "XFCE": 199 return "XFCE" 200 elif (desktop or detected) == "Mac OS X": 201 return "Mac OS X" 202 elif (desktop or detected) == "X11": 203 return "X11" 204 else: 205 return None 206 207 def is_standard(): 208 209 """ 210 Return whether the current desktop supports standardised application 211 launching. 212 """ 213 214 return os.environ.has_key("DESKTOP_LAUNCH") 215 216 # Activity functions. 217 218 def open(url, desktop=None, wait=0): 219 220 """ 221 Open the 'url' in the current desktop's preferred file browser. If the 222 optional 'desktop' parameter is specified then attempt to use that 223 particular desktop environment's mechanisms to open the 'url' instead of 224 guessing or detecting which environment is being used. 225 226 Suggested values for 'desktop' are "standard", "KDE", "GNOME", "XFCE", 227 "Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH environment 228 variable to open the specified 'url'. DESKTOP_LAUNCH should be a command, 229 possibly followed by arguments, and must have any special characters 230 shell-escaped. 231 232 The process identifier of the "opener" (ie. viewer, editor, browser or 233 program) associated with the 'url' is returned by this function. If the 234 process identifier cannot be determined, None is returned. 235 236 An optional 'wait' parameter is also available for advanced usage and, if 237 'wait' is set to a true value, this function will wait for the launching 238 mechanism to complete before returning (as opposed to immediately returning 239 as is the default behaviour). 240 """ 241 242 # Decide on the desktop environment in use. 243 244 desktop_in_use = use_desktop(desktop) 245 246 if desktop_in_use == "standard": 247 arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)]) 248 return _run(arg, 1, wait) 249 250 elif desktop_in_use == "Windows": 251 # NOTE: This returns None in current implementations. 252 return os.startfile(url) 253 254 elif desktop_in_use == "KDE": 255 cmd = ["kfmclient", "exec", url] 256 257 elif desktop_in_use == "GNOME": 258 cmd = ["gnome-open", url] 259 260 elif desktop_in_use == "XFCE": 261 cmd = ["exo-open", url] 262 263 elif desktop_in_use == "Mac OS X": 264 cmd = ["open", url] 265 266 elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"): 267 cmd = [os.environ["BROWSER"], url] 268 269 # Finish with an error where no suitable desktop was identified. 270 271 else: 272 raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use 273 274 return _run(cmd, 0, wait) 275 276 # vim: tabstop=4 expandtab shiftwidth=4