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 return "KDE" 160 elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \ 161 os.environ.has_key("GNOME_KEYRING_SOCKET"): 162 return "GNOME" 163 elif sys.platform == "darwin": 164 return "Mac OS X" 165 elif hasattr(os, "startfile"): 166 return "Windows" 167 elif _is_xfce(): 168 return "XFCE" 169 170 # KDE, GNOME and XFCE run on X11, so we have to test for X11 last. 171 172 if _is_x11(): 173 return "X11" 174 else: 175 return None 176 177 def use_desktop(desktop): 178 179 """ 180 Decide which desktop should be used, based on the detected desktop and a 181 supplied 'desktop' argument (which may be None). Return an identifier 182 indicating the desktop type as being either "standard" or one of the results 183 from the 'get_desktop' function. 184 """ 185 186 # Attempt to detect a desktop environment. 187 188 detected = get_desktop() 189 190 # Start with desktops whose existence can be easily tested. 191 192 if (desktop is None or desktop == "standard") and is_standard(): 193 return "standard" 194 elif (desktop is None or desktop == "Windows") and detected == "Windows": 195 return "Windows" 196 197 # Test for desktops where the overriding is not verified. 198 199 elif (desktop or detected) == "KDE": 200 return "KDE" 201 elif (desktop or detected) == "GNOME": 202 return "GNOME" 203 elif (desktop or detected) == "XFCE": 204 return "XFCE" 205 elif (desktop or detected) == "Mac OS X": 206 return "Mac OS X" 207 elif (desktop or detected) == "X11": 208 return "X11" 209 else: 210 return None 211 212 def is_standard(): 213 214 """ 215 Return whether the current desktop supports standardised application 216 launching. 217 """ 218 219 return os.environ.has_key("DESKTOP_LAUNCH") 220 221 # Activity functions. 222 223 def open(url, desktop=None, wait=0): 224 225 """ 226 Open the 'url' in the current desktop's preferred file browser. If the 227 optional 'desktop' parameter is specified then attempt to use that 228 particular desktop environment's mechanisms to open the 'url' instead of 229 guessing or detecting which environment is being used. 230 231 Suggested values for 'desktop' are "standard", "KDE", "GNOME", "XFCE", 232 "Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH environment 233 variable to open the specified 'url'. DESKTOP_LAUNCH should be a command, 234 possibly followed by arguments, and must have any special characters 235 shell-escaped. 236 237 The process identifier of the "opener" (ie. viewer, editor, browser or 238 program) associated with the 'url' is returned by this function. If the 239 process identifier cannot be determined, None is returned. 240 241 An optional 'wait' parameter is also available for advanced usage and, if 242 'wait' is set to a true value, this function will wait for the launching 243 mechanism to complete before returning (as opposed to immediately returning 244 as is the default behaviour). 245 """ 246 247 # Decide on the desktop environment in use. 248 249 desktop_in_use = use_desktop(desktop) 250 251 if desktop_in_use == "standard": 252 arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)]) 253 return _run(arg, 1, wait) 254 255 elif desktop_in_use == "Windows": 256 # NOTE: This returns None in current implementations. 257 return os.startfile(url) 258 259 elif desktop_in_use == "KDE": 260 cmd = ["kfmclient", "exec", url] 261 262 elif desktop_in_use == "GNOME": 263 cmd = ["gnome-open", url] 264 265 elif desktop_in_use == "XFCE": 266 cmd = ["exo-open", url] 267 268 elif desktop_in_use == "Mac OS X": 269 cmd = ["open", url] 270 271 elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"): 272 cmd = [os.environ["BROWSER"], url] 273 274 # Finish with an error where no suitable desktop was identified. 275 276 else: 277 raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use 278 279 return _run(cmd, 0, wait) 280 281 # vim: tabstop=4 expandtab shiftwidth=4