desktop

Annotated desktop/windows.py

84:536057654c88
2014-01-13 Paul Boddie Added missing KDE 4 support to the desktop.dialog module. Updated copyright and version information.
paulb@46 1
#!/usr/bin/env python
paulb@46 2
paulb@46 3
"""
paulb@46 4
Simple desktop window enumeration for Python.
paulb@46 5
paul@65 6
Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
paulb@46 7
paul@68 8
This program is free software; you can redistribute it and/or modify it under
paul@68 9
the terms of the GNU Lesser General Public License as published by the Free
paul@68 10
Software Foundation; either version 3 of the License, or (at your option) any
paul@68 11
later version.
paulb@46 12
paul@68 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@68 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@68 15
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
paul@68 16
details.
paulb@46 17
paul@68 18
You should have received a copy of the GNU Lesser General Public License along
paul@68 19
with this program.  If not, see <http://www.gnu.org/licenses/>.
paulb@46 20
paulb@46 21
--------
paulb@46 22
paulb@46 23
Finding Open Windows on the Desktop
paulb@46 24
-----------------------------------
paulb@46 25
paulb@46 26
To obtain a list of windows, use the desktop.windows.list function as follows:
paulb@46 27
paulb@51 28
windows = desktop.windows.list()
paulb@51 29
paul@64 30
To obtain the root window, typically the desktop background, use the
paul@64 31
desktop.windows.root function as follows:
paul@64 32
paul@64 33
root = desktop.windows.root()
paul@64 34
paulb@51 35
Each window object can be inspected through a number of methods. For example:
paulb@51 36
paulb@51 37
name = window.name()
paulb@51 38
width, height = window.size()
paulb@51 39
x, y = window.position()
paulb@51 40
child_windows = window.children()
paulb@51 41
paulb@51 42
See the desktop.windows.Window class for more information.
paulb@46 43
"""
paulb@46 44
paulb@46 45
from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
paul@65 46
import re
paul@65 47
paul@65 48
# System functions.
paulb@46 49
paul@67 50
def _xwininfo(identifier, action):
paul@67 51
    if identifier is None:
paul@67 52
        args = "-root"
paul@67 53
    else:
paul@67 54
        args = "-id " + identifier
paul@67 55
paul@67 56
    s = _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (args, action), shell=1)
paul@67 57
paul@67 58
    # Return a mapping of keys to values for the "stats" action.
paul@67 59
paul@67 60
    if action == "stats":
paul@67 61
        d = {}
paul@67 62
        for line in s.split("\n"):
paul@67 63
            fields = line.split(":")
paul@67 64
            if len(fields) < 2:
paul@67 65
                continue
paul@67 66
            key, value = fields[0].strip(), ":".join(fields[1:]).strip()
paul@67 67
            d[key] = value
paul@67 68
paul@67 69
        return d
paul@67 70
paul@67 71
    # Otherwise, return the raw output.
paul@67 72
paul@67 73
    else:
paul@67 74
        return s
paulb@51 75
paulb@51 76
def _get_int_properties(d, properties):
paulb@51 77
    results = []
paulb@51 78
    for property in properties:
paul@65 79
        results.append(int(d[property]))
paulb@51 80
    return results
paulb@51 81
paul@65 82
# Finder functions.
paul@65 83
paul@65 84
def find_all(name):
paul@65 85
    return 1
paul@65 86
paul@65 87
def find_named(name):
paul@65 88
    return name is not None
paul@65 89
paul@65 90
def find_by_name(name):
paul@65 91
    return lambda n, t=name: n == t
paul@65 92
paulb@51 93
# Window classes.
paulb@51 94
# NOTE: X11 is the only supported desktop so far.
paulb@51 95
paulb@51 96
class Window:
paulb@51 97
paulb@51 98
    "A window on the desktop."
paulb@51 99
paul@65 100
    _name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
paul@66 101
    _absent_names = "(has no name)", "(the root window) (has no name)"
paul@65 102
paulb@51 103
    def __init__(self, identifier):
paulb@51 104
paulb@51 105
        "Initialise the window with the given 'identifier'."
paulb@51 106
paulb@51 107
        self.identifier = identifier
paulb@51 108
paul@65 109
        # Finder methods (from above).
paul@65 110
paul@65 111
        self.find_all = find_all
paul@65 112
        self.find_named = find_named
paul@65 113
        self.find_by_name = find_by_name
paul@65 114
paulb@51 115
    def __repr__(self):
paulb@51 116
        return "Window(%r)" % self.identifier
paulb@51 117
paul@65 118
    # Methods which deal with the underlying commands.
paul@65 119
paul@65 120
    def _get_handle_and_name(self, text):
paul@65 121
        fields = text.strip().split(" ")
paul@65 122
        handle = fields[0]
paul@65 123
paul@65 124
        # Get the "<name>" part, stripping off the quotes.
paul@65 125
paul@65 126
        name = " ".join(fields[1:])
paul@65 127
        if len(name) > 1 and name[0] == '"' and name[-1] == '"':
paul@65 128
            name = name[1:-1]
paulb@51 129
paul@66 130
        if name in self._absent_names:
paul@65 131
            return handle, None
paul@65 132
        else:
paul@65 133
            return handle, name
paul@65 134
paul@65 135
    def _get_this_handle_and_name(self, line):
paul@65 136
        fields = line.split(":")
paul@67 137
        return self._get_handle_and_name(":".join(fields[1:]))
paulb@51 138
paul@65 139
    def _get_descendant_handle_and_name(self, line):
paul@65 140
        match = self._name_pattern.search(line)
paul@65 141
        if match:
paul@65 142
            return self._get_handle_and_name(line[:match.start()].strip())
paul@65 143
        else:
paul@65 144
            raise OSError, "Window information from %r did not contain window details." % line
paul@65 145
paul@65 146
    def _descendants(self, s, fn):
paulb@51 147
        handles = []
paulb@51 148
        adding = 0
paulb@51 149
        for line in s.split("\n"):
paul@65 150
            if line.endswith("child:") or line.endswith("children:"):
paul@65 151
                if not adding:
paul@65 152
                    adding = 1
paulb@51 153
            elif adding and line:
paul@65 154
                handle, name = self._get_descendant_handle_and_name(line)
paul@65 155
                if fn(name):
paul@65 156
                    handles.append(handle)
paulb@51 157
        return [Window(handle) for handle in handles]
paulb@51 158
paul@65 159
    # Public methods.
paul@65 160
paul@65 161
    def children(self, all=0):
paul@65 162
paul@65 163
        """
paul@65 164
        Return a list of windows which are children of this window. If the
paul@65 165
        optional 'all' parameter is set to a true value, all such windows will
paul@65 166
        be returned regardless of whether they have any name information.
paul@65 167
        """
paul@65 168
paul@67 169
        s = _xwininfo(self.identifier, "children")
paul@65 170
        return self._descendants(s, all and self.find_all or self.find_named)
paul@65 171
paul@65 172
    def descendants(self, all=0):
paul@65 173
paul@65 174
        """
paul@65 175
        Return a list of windows which are descendants of this window. If the
paul@65 176
        optional 'all' parameter is set to a true value, all such windows will
paul@65 177
        be returned regardless of whether they have any name information.
paul@65 178
        """
paul@65 179
paul@67 180
        s = _xwininfo(self.identifier, "tree")
paul@65 181
        return self._descendants(s, all and self.find_all or self.find_named)
paul@65 182
paul@65 183
    def find(self, callable):
paul@65 184
paul@65 185
        """
paul@65 186
        Return windows using the given 'callable' (returning a true or a false
paul@65 187
        value when invoked with a window name) for descendants of this window.
paul@65 188
        """
paul@65 189
paul@67 190
        s = _xwininfo(self.identifier, "tree")
paul@65 191
        return self._descendants(s, callable)
paul@65 192
paulb@51 193
    def name(self):
paulb@51 194
paulb@51 195
        "Return the name of the window."
paulb@51 196
paul@67 197
        d = _xwininfo(self.identifier, "stats")
paulb@51 198
paul@67 199
        # Format is 'xwininfo: Window id: <handle> "<name>"
paulb@51 200
paul@67 201
        return self._get_this_handle_and_name(d["xwininfo"])[1]
paulb@51 202
paulb@51 203
    def size(self):
paulb@51 204
paulb@51 205
        "Return a tuple containing the width and height of this window."
paulb@51 206
paul@67 207
        d = _xwininfo(self.identifier, "stats")
paulb@51 208
        return _get_int_properties(d, ["Width", "Height"])
paulb@51 209
paulb@51 210
    def position(self):
paulb@51 211
paulb@51 212
        "Return a tuple containing the upper left co-ordinates of this window."
paulb@51 213
paul@67 214
        d = _xwininfo(self.identifier, "stats")
paulb@51 215
        return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
paulb@51 216
paul@66 217
    def displayed(self):
paul@65 218
paul@66 219
        """
paul@66 220
        Return whether the window is displayed in some way (but not necessarily
paul@66 221
        visible on the current screen).
paul@66 222
        """
paul@65 223
paul@67 224
        d = _xwininfo(self.identifier, "stats")
paul@65 225
        return d["Map State"] != "IsUnviewable"
paul@65 226
paul@66 227
    def visible(self):
paul@66 228
paul@66 229
        "Return whether the window is displayed and visible."
paul@66 230
paul@67 231
        d = _xwininfo(self.identifier, "stats")
paul@66 232
        return d["Map State"] == "IsViewable"
paul@66 233
paulb@46 234
def list(desktop=None):
paulb@46 235
paulb@46 236
    """
paulb@51 237
    Return a list of windows for the current desktop. If the optional 'desktop'
paulb@51 238
    parameter is specified then attempt to use that particular desktop
paulb@46 239
    environment's mechanisms to look for windows.
paulb@46 240
    """
paulb@46 241
paul@65 242
    root_window = root(desktop)
paul@66 243
    window_list = [window for window in root_window.descendants() if window.displayed()]
paul@65 244
    window_list.insert(0, root_window)
paul@65 245
    return window_list
paulb@46 246
paul@64 247
def root(desktop=None):
paul@64 248
paul@64 249
    """
paul@64 250
    Return the root window for the current desktop. If the optional 'desktop'
paul@64 251
    parameter is specified then attempt to use that particular desktop
paul@64 252
    environment's mechanisms to look for windows.
paul@64 253
    """
paul@64 254
paul@64 255
    # NOTE: The desktop parameter is currently ignored and X11 is tested for
paul@64 256
    # NOTE: directly.
paul@64 257
paul@64 258
    if _is_x11():
paul@64 259
        return Window(None)
paul@64 260
    else:
paul@64 261
        raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
paul@64 262
paul@65 263
def find(callable, desktop=None):
paul@65 264
paul@65 265
    """
paul@65 266
    Find and return windows using the given 'callable' for the current desktop.
paul@65 267
    If the optional 'desktop' parameter is specified then attempt to use that
paul@65 268
    particular desktop environment's mechanisms to look for windows.
paul@65 269
    """
paul@65 270
paul@65 271
    return root(desktop).find(callable)
paul@65 272
paulb@46 273
# vim: tabstop=4 expandtab shiftwidth=4