desktop

Annotated desktop/__init__.py

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