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