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