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