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 Paul Boddie <paul@boddie.org.uk> 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2.1 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; if not, write to the Free Software 22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 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.3.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 return "KDE" 161 elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \ 162 os.environ.has_key("GNOME_KEYRING_SOCKET"): 163 return "GNOME" 164 elif sys.platform == "darwin": 165 return "Mac OS X" 166 elif hasattr(os, "startfile"): 167 return "Windows" 168 elif _is_xfce(): 169 return "XFCE" 170 171 # KDE, GNOME and XFCE run on X11, so we have to test for X11 last. 172 173 if _is_x11(): 174 return "X11" 175 else: 176 return None 177 178 def use_desktop(desktop): 179 180 """ 181 Decide which desktop should be used, based on the detected desktop and a 182 supplied 'desktop' argument (which may be None). Return an identifier 183 indicating the desktop type as being either "standard" or one of the results 184 from the 'get_desktop' function. 185 """ 186 187 # Attempt to detect a desktop environment. 188 189 detected = get_desktop() 190 191 # Start with desktops whose existence can be easily tested. 192 193 if (desktop is None or desktop == "standard") and is_standard(): 194 return "standard" 195 elif (desktop is None or desktop == "Windows") and detected == "Windows": 196 return "Windows" 197 198 # Test for desktops where the overriding is not verified. 199 200 elif (desktop or detected) == "KDE": 201 return "KDE" 202 elif (desktop or detected) == "GNOME": 203 return "GNOME" 204 elif (desktop or detected) == "XFCE": 205 return "XFCE" 206 elif (desktop or detected) == "Mac OS X": 207 return "Mac OS X" 208 elif (desktop or detected) == "X11": 209 return "X11" 210 else: 211 return None 212 213 def is_standard(): 214 215 """ 216 Return whether the current desktop supports standardised application 217 launching. 218 """ 219 220 return os.environ.has_key("DESKTOP_LAUNCH") 221 222 # Activity functions. 223 224 def open(url, desktop=None, wait=0): 225 226 """ 227 Open the 'url' in the current desktop's preferred file browser. If the 228 optional 'desktop' parameter is specified then attempt to use that 229 particular desktop environment's mechanisms to open the 'url' instead of 230 guessing or detecting which environment is being used. 231 232 Suggested values for 'desktop' are "standard", "KDE", "GNOME", "XFCE", 233 "Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH environment 234 variable to open the specified 'url'. DESKTOP_LAUNCH should be a command, 235 possibly followed by arguments, and must have any special characters 236 shell-escaped. 237 238 The process identifier of the "opener" (ie. viewer, editor, browser or 239 program) associated with the 'url' is returned by this function. If the 240 process identifier cannot be determined, None is returned. 241 242 An optional 'wait' parameter is also available for advanced usage and, if 243 'wait' is set to a true value, this function will wait for the launching 244 mechanism to complete before returning (as opposed to immediately returning 245 as is the default behaviour). 246 """ 247 248 # Decide on the desktop environment in use. 249 250 desktop_in_use = use_desktop(desktop) 251 252 if desktop_in_use == "standard": 253 arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)]) 254 return _run(arg, 1, wait) 255 256 elif desktop_in_use == "Windows": 257 # NOTE: This returns None in current implementations. 258 return os.startfile(url) 259 260 elif desktop_in_use == "KDE": 261 cmd = ["kfmclient", "exec", url] 262 263 elif desktop_in_use == "GNOME": 264 cmd = ["gnome-open", url] 265 266 elif desktop_in_use == "XFCE": 267 cmd = ["exo-open", url] 268 269 elif desktop_in_use == "Mac OS X": 270 cmd = ["open", url] 271 272 elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"): 273 cmd = [os.environ["BROWSER"], url] 274 275 # Finish with an error where no suitable desktop was identified. 276 277 else: 278 raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use 279 280 return _run(cmd, 0, wait) 281 282 # vim: tabstop=4 expandtab shiftwidth=4