1.1 --- a/desktop/windows.py Sun Nov 30 01:17:09 2008 +0100
1.2 +++ b/desktop/windows.py Wed Jun 03 21:28:49 2009 +0200
1.3 @@ -3,7 +3,7 @@
1.4 """
1.5 Simple desktop window enumeration for Python.
1.6
1.7 -Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk>
1.8 +Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
1.9
1.10 This library is free software; you can redistribute it and/or
1.11 modify it under the terms of the GNU Lesser General Public
1.12 @@ -44,6 +44,9 @@
1.13 """
1.14
1.15 from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
1.16 +import re
1.17 +
1.18 +# System functions.
1.19
1.20 def _xwininfo(s):
1.21 d = {}
1.22 @@ -51,16 +54,27 @@
1.23 fields = line.split(":")
1.24 if len(fields) < 2:
1.25 continue
1.26 - key, values = fields[0].strip(), fields[1:]
1.27 - d[key] = values
1.28 + key, value = fields[0].strip(), ":".join(fields[1:]).strip()
1.29 + d[key] = value
1.30 return d
1.31
1.32 def _get_int_properties(d, properties):
1.33 results = []
1.34 for property in properties:
1.35 - results.append(int(d[property][0].strip()))
1.36 + results.append(int(d[property]))
1.37 return results
1.38
1.39 +# Finder functions.
1.40 +
1.41 +def find_all(name):
1.42 + return 1
1.43 +
1.44 +def find_named(name):
1.45 + return name is not None
1.46 +
1.47 +def find_by_name(name):
1.48 + return lambda n, t=name: n == t
1.49 +
1.50 # Window classes.
1.51 # NOTE: X11 is the only supported desktop so far.
1.52
1.53 @@ -68,52 +82,118 @@
1.54
1.55 "A window on the desktop."
1.56
1.57 + _name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
1.58 +
1.59 def __init__(self, identifier):
1.60
1.61 "Initialise the window with the given 'identifier'."
1.62
1.63 self.identifier = identifier
1.64
1.65 + # Finder methods (from above).
1.66 +
1.67 + self.find_all = find_all
1.68 + self.find_named = find_named
1.69 + self.find_by_name = find_by_name
1.70 +
1.71 def __repr__(self):
1.72 return "Window(%r)" % self.identifier
1.73
1.74 - def _get_identifier(self):
1.75 + # Methods which deal with the underlying commands.
1.76 +
1.77 + def _get_identifier_args(self):
1.78 if self.identifier is None:
1.79 return "-root"
1.80 else:
1.81 return "-id " + self.identifier
1.82
1.83 - def children(self):
1.84 + def _get_command_output(self, action):
1.85 + return _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (self._get_identifier_args(), action), shell=1)
1.86 +
1.87 + def _get_handle_and_name(self, text):
1.88 + fields = text.strip().split(" ")
1.89 + handle = fields[0]
1.90 +
1.91 + # Get the "<name>" part, stripping off the quotes.
1.92 +
1.93 + name = " ".join(fields[1:])
1.94 + if len(name) > 1 and name[0] == '"' and name[-1] == '"':
1.95 + name = name[1:-1]
1.96
1.97 - "Return a list of windows which are children of this window."
1.98 + if name == "(has no name)":
1.99 + return handle, None
1.100 + else:
1.101 + return handle, name
1.102 +
1.103 + def _get_this_handle_and_name(self, line):
1.104 + fields = line.split(":")
1.105 + return self._get_handle_and_name(":".join(fields[2:]))
1.106
1.107 - s = _readfrom(_get_x11_vars() + "xwininfo %s -children" % self._get_identifier(), shell=1)
1.108 + def _get_descendant_handle_and_name(self, line):
1.109 + match = self._name_pattern.search(line)
1.110 + if match:
1.111 + return self._get_handle_and_name(line[:match.start()].strip())
1.112 + else:
1.113 + raise OSError, "Window information from %r did not contain window details." % line
1.114 +
1.115 + def _descendants(self, s, fn):
1.116 handles = []
1.117 adding = 0
1.118 for line in s.split("\n"):
1.119 - if not adding and line.endswith("children:"):
1.120 - adding = 1
1.121 + if line.endswith("child:") or line.endswith("children:"):
1.122 + if not adding:
1.123 + adding = 1
1.124 elif adding and line:
1.125 - handles.append(line.strip().split()[0])
1.126 + handle, name = self._get_descendant_handle_and_name(line)
1.127 + if fn(name):
1.128 + handles.append(handle)
1.129 return [Window(handle) for handle in handles]
1.130
1.131 + # Public methods.
1.132 +
1.133 + def children(self, all=0):
1.134 +
1.135 + """
1.136 + Return a list of windows which are children of this window. If the
1.137 + optional 'all' parameter is set to a true value, all such windows will
1.138 + be returned regardless of whether they have any name information.
1.139 + """
1.140 +
1.141 + s = self._get_command_output("children")
1.142 + return self._descendants(s, all and self.find_all or self.find_named)
1.143 +
1.144 + def descendants(self, all=0):
1.145 +
1.146 + """
1.147 + Return a list of windows which are descendants of this window. If the
1.148 + optional 'all' parameter is set to a true value, all such windows will
1.149 + be returned regardless of whether they have any name information.
1.150 + """
1.151 +
1.152 + s = self._get_command_output("tree")
1.153 + return self._descendants(s, all and self.find_all or self.find_named)
1.154 +
1.155 + def find(self, callable):
1.156 +
1.157 + """
1.158 + Return windows using the given 'callable' (returning a true or a false
1.159 + value when invoked with a window name) for descendants of this window.
1.160 + """
1.161 +
1.162 + s = self._get_command_output("tree")
1.163 + return self._descendants(s, callable)
1.164 +
1.165 def name(self):
1.166
1.167 "Return the name of the window."
1.168
1.169 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
1.170 + s = self._get_command_output("stats")
1.171 for line in s.split("\n"):
1.172 if line.startswith("xwininfo:"):
1.173
1.174 # Format is 'xwininfo: Window id: <handle> "<name>"
1.175
1.176 - fields = line.split(":")
1.177 - handle_and_name = fields[2].strip()
1.178 - fields2 = handle_and_name.split(" ")
1.179 -
1.180 - # Get the "<name>" part, stripping off the quotes.
1.181 -
1.182 - return " ".join(fields2[1:]).strip('"')
1.183 + return self._get_this_handle_and_name(line)[1]
1.184
1.185 return None
1.186
1.187 @@ -121,7 +201,7 @@
1.188
1.189 "Return a tuple containing the width and height of this window."
1.190
1.191 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
1.192 + s = self._get_command_output("stats")
1.193 d = _xwininfo(s)
1.194 return _get_int_properties(d, ["Width", "Height"])
1.195
1.196 @@ -129,10 +209,18 @@
1.197
1.198 "Return a tuple containing the upper left co-ordinates of this window."
1.199
1.200 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
1.201 + s = self._get_command_output("stats")
1.202 d = _xwininfo(s)
1.203 return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
1.204
1.205 + def visible(self):
1.206 +
1.207 + "Return whether the window is in some way visible."
1.208 +
1.209 + s = self._get_command_output("stats")
1.210 + d = _xwininfo(s)
1.211 + return d["Map State"] != "IsUnviewable"
1.212 +
1.213 def list(desktop=None):
1.214
1.215 """
1.216 @@ -141,25 +229,10 @@
1.217 environment's mechanisms to look for windows.
1.218 """
1.219
1.220 - # NOTE: The desktop parameter is currently ignored and X11 is tested for
1.221 - # NOTE: directly.
1.222 -
1.223 - if _is_x11():
1.224 - s = _readfrom(_get_x11_vars() + "xlsclients -a -l", shell=1)
1.225 - prefix = "Window "
1.226 - prefix_end = len(prefix)
1.227 -
1.228 - # Include the root window.
1.229 -
1.230 - handles = [None]
1.231 -
1.232 - for line in s.split("\n"):
1.233 - if line.startswith(prefix):
1.234 - handles.append(line[prefix_end:-1]) # NOTE: Assume ":" at end.
1.235 - else:
1.236 - raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
1.237 -
1.238 - return [Window(handle) for handle in handles]
1.239 + root_window = root(desktop)
1.240 + window_list = [window for window in root_window.descendants() if window.visible()]
1.241 + window_list.insert(0, root_window)
1.242 + return window_list
1.243
1.244 def root(desktop=None):
1.245
1.246 @@ -177,4 +250,14 @@
1.247 else:
1.248 raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
1.249
1.250 +def find(callable, desktop=None):
1.251 +
1.252 + """
1.253 + Find and return windows using the given 'callable' for the current desktop.
1.254 + If the optional 'desktop' parameter is specified then attempt to use that
1.255 + particular desktop environment's mechanisms to look for windows.
1.256 + """
1.257 +
1.258 + return root(desktop).find(callable)
1.259 +
1.260 # vim: tabstop=4 expandtab shiftwidth=4