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 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 71 __version__ = "0.2.4" 72 73 import os 74 import sys 75 76 # Provide suitable process creation functions. 77 78 try: 79 import subprocess 80 def _run(cmd, shell, wait): 81 opener = subprocess.Popen(cmd, shell=shell) 82 if wait: opener.wait() 83 return opener.pid 84 85 def _readfrom(cmd, shell): 86 opener = subprocess.Popen(cmd, shell=shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 87 opener.stdin.close() 88 return opener.stdout.read() 89 90 def _status(cmd, shell): 91 opener = subprocess.Popen(cmd, shell=shell) 92 opener.wait() 93 return opener.returncode == 0 94 95 except ImportError: 96 import popen2 97 def _run(cmd, shell, wait): 98 opener = popen2.Popen3(cmd) 99 if wait: opener.wait() 100 return opener.pid 101 102 def _readfrom(cmd, shell): 103 opener = popen2.Popen3(cmd) 104 opener.tochild.close() 105 opener.childerr.close() 106 return opener.fromchild.read() 107 108 def _status(cmd, shell): 109 opener = popen2.Popen3(cmd) 110 opener.wait() 111 return opener.poll() == 0 112 113 import commands 114 115 # Private functions. 116 117 def _is_xfce(): 118 119 "Return whether XFCE is in use." 120 121 # XFCE detection involves testing the output of a program. 122 123 try: 124 if not os.environ.get("DISPLAY", "").strip(): 125 vars = "DISPLAY=:0.0 " 126 else: 127 vars = "" 128 return _readfrom(vars + "xprop -root _DT_SAVE_MODE", shell=1).strip().endswith(' = "xfce4"') 129 130 except OSError: 131 return 0 132 133 # Introspection functions. 134 135 def get_desktop(): 136 137 """ 138 Detect the current desktop environment, returning the name of the 139 environment. If no environment could be detected, None is returned. 140 """ 141 142 if os.environ.has_key("KDE_FULL_SESSION") or \ 143 os.environ.has_key("KDE_MULTIHEAD"): 144 return "KDE" 145 elif os.environ.has_key("GNOME_DESKTOP_SESSION_ID") or \ 146 os.environ.has_key("GNOME_KEYRING_SOCKET"): 147 return "GNOME" 148 elif sys.platform == "darwin": 149 return "Mac OS X" 150 elif hasattr(os, "startfile"): 151 return "Windows" 152 elif _is_xfce(): 153 return "XFCE" 154 155 # XFCE runs on X11, so we have to test for X11 last. 156 157 if os.environ.has_key("DISPLAY"): 158 return "X11" 159 else: 160 return None 161 162 def use_desktop(desktop): 163 164 """ 165 Decide which desktop should be used, based on the detected desktop and a 166 supplied 'desktop' argument (which may be None). Return an identifier 167 indicating the desktop type as being either "standard" or one of the results 168 from the 'get_desktop' function. 169 """ 170 171 # Attempt to detect a desktop environment. 172 173 detected = get_desktop() 174 175 # Start with desktops whose existence can be easily tested. 176 177 if (desktop is None or desktop == "standard") and is_standard(): 178 return "standard" 179 elif (desktop is None or desktop == "Windows") and detected == "Windows": 180 return "Windows" 181 182 # Test for desktops where the overriding is not verified. 183 184 elif (desktop or detected) == "KDE": 185 return "KDE" 186 elif (desktop or detected) == "GNOME": 187 return "GNOME" 188 elif (desktop or detected) == "XFCE": 189 return "XFCE" 190 elif (desktop or detected) == "Mac OS X": 191 return "Mac OS X" 192 elif (desktop or detected) == "X11": 193 return "X11" 194 else: 195 return None 196 197 def is_standard(): 198 199 """ 200 Return whether the current desktop supports standardised application 201 launching. 202 """ 203 204 return os.environ.has_key("DESKTOP_LAUNCH") 205 206 # Activity functions. 207 208 def open(url, desktop=None, wait=0): 209 210 """ 211 Open the 'url' in the current desktop's preferred file browser. If the 212 optional 'desktop' parameter is specified then attempt to use that 213 particular desktop environment's mechanisms to open the 'url' instead of 214 guessing or detecting which environment is being used. 215 216 Suggested values for 'desktop' are "standard", "KDE", "GNOME", "XFCE", 217 "Mac OS X", "Windows" where "standard" employs a DESKTOP_LAUNCH environment 218 variable to open the specified 'url'. DESKTOP_LAUNCH should be a command, 219 possibly followed by arguments, and must have any special characters 220 shell-escaped. 221 222 The process identifier of the "opener" (ie. viewer, editor, browser or 223 program) associated with the 'url' is returned by this function. If the 224 process identifier cannot be determined, None is returned. 225 226 An optional 'wait' parameter is also available for advanced usage and, if 227 'wait' is set to a true value, this function will wait for the launching 228 mechanism to complete before returning (as opposed to immediately returning 229 as is the default behaviour). 230 """ 231 232 # Decide on the desktop environment in use. 233 234 desktop_in_use = use_desktop(desktop) 235 236 if desktop_in_use == "standard": 237 arg = "".join([os.environ["DESKTOP_LAUNCH"], commands.mkarg(url)]) 238 return _run(arg, 1, wait) 239 240 elif desktop_in_use == "Windows": 241 # NOTE: This returns None in current implementations. 242 return os.startfile(url) 243 244 elif desktop_in_use == "KDE": 245 cmd = ["kfmclient", "exec", url] 246 247 elif desktop_in_use == "GNOME": 248 cmd = ["gnome-open", url] 249 250 elif desktop_in_use == "XFCE": 251 cmd = ["exo-open", url] 252 253 elif desktop_in_use == "Mac OS X": 254 cmd = ["open", url] 255 256 elif desktop_in_use == "X11" and os.environ.has_key("BROWSER"): 257 cmd = [os.environ["BROWSER"], url] 258 259 # Finish with an error where no suitable desktop was identified. 260 261 else: 262 raise OSError, "Desktop '%s' not supported (neither DESKTOP_LAUNCH nor os.startfile could be used)" % desktop_in_use 263 264 return _run(cmd, 0, wait) 265 266 # vim: tabstop=4 expandtab shiftwidth=4