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