1.1 --- a/README.txt Sun Nov 30 01:17:09 2008 +0100
1.2 +++ b/README.txt Wed Jun 03 21:28:49 2009 +0200
1.3 @@ -97,7 +97,7 @@
1.4 Check the setup.py file.
1.5 Tag, export.
1.6 Archive, upload.
1.7 -Update PyPI, PythonInfo Wiki, Vaults of Parnassus entries.
1.8 +Update PyPI entry.
1.9
1.10 Making Packages
1.11 ---------------
2.1 --- a/desktop/windows.py Sun Nov 30 01:17:09 2008 +0100
2.2 +++ b/desktop/windows.py Wed Jun 03 21:28:49 2009 +0200
2.3 @@ -3,7 +3,7 @@
2.4 """
2.5 Simple desktop window enumeration for Python.
2.6
2.7 -Copyright (C) 2007, 2008 Paul Boddie <paul@boddie.org.uk>
2.8 +Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
2.9
2.10 This library is free software; you can redistribute it and/or
2.11 modify it under the terms of the GNU Lesser General Public
2.12 @@ -44,6 +44,9 @@
2.13 """
2.14
2.15 from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
2.16 +import re
2.17 +
2.18 +# System functions.
2.19
2.20 def _xwininfo(s):
2.21 d = {}
2.22 @@ -51,16 +54,27 @@
2.23 fields = line.split(":")
2.24 if len(fields) < 2:
2.25 continue
2.26 - key, values = fields[0].strip(), fields[1:]
2.27 - d[key] = values
2.28 + key, value = fields[0].strip(), ":".join(fields[1:]).strip()
2.29 + d[key] = value
2.30 return d
2.31
2.32 def _get_int_properties(d, properties):
2.33 results = []
2.34 for property in properties:
2.35 - results.append(int(d[property][0].strip()))
2.36 + results.append(int(d[property]))
2.37 return results
2.38
2.39 +# Finder functions.
2.40 +
2.41 +def find_all(name):
2.42 + return 1
2.43 +
2.44 +def find_named(name):
2.45 + return name is not None
2.46 +
2.47 +def find_by_name(name):
2.48 + return lambda n, t=name: n == t
2.49 +
2.50 # Window classes.
2.51 # NOTE: X11 is the only supported desktop so far.
2.52
2.53 @@ -68,52 +82,118 @@
2.54
2.55 "A window on the desktop."
2.56
2.57 + _name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
2.58 +
2.59 def __init__(self, identifier):
2.60
2.61 "Initialise the window with the given 'identifier'."
2.62
2.63 self.identifier = identifier
2.64
2.65 + # Finder methods (from above).
2.66 +
2.67 + self.find_all = find_all
2.68 + self.find_named = find_named
2.69 + self.find_by_name = find_by_name
2.70 +
2.71 def __repr__(self):
2.72 return "Window(%r)" % self.identifier
2.73
2.74 - def _get_identifier(self):
2.75 + # Methods which deal with the underlying commands.
2.76 +
2.77 + def _get_identifier_args(self):
2.78 if self.identifier is None:
2.79 return "-root"
2.80 else:
2.81 return "-id " + self.identifier
2.82
2.83 - def children(self):
2.84 + def _get_command_output(self, action):
2.85 + return _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (self._get_identifier_args(), action), shell=1)
2.86 +
2.87 + def _get_handle_and_name(self, text):
2.88 + fields = text.strip().split(" ")
2.89 + handle = fields[0]
2.90 +
2.91 + # Get the "<name>" part, stripping off the quotes.
2.92 +
2.93 + name = " ".join(fields[1:])
2.94 + if len(name) > 1 and name[0] == '"' and name[-1] == '"':
2.95 + name = name[1:-1]
2.96
2.97 - "Return a list of windows which are children of this window."
2.98 + if name == "(has no name)":
2.99 + return handle, None
2.100 + else:
2.101 + return handle, name
2.102 +
2.103 + def _get_this_handle_and_name(self, line):
2.104 + fields = line.split(":")
2.105 + return self._get_handle_and_name(":".join(fields[2:]))
2.106
2.107 - s = _readfrom(_get_x11_vars() + "xwininfo %s -children" % self._get_identifier(), shell=1)
2.108 + def _get_descendant_handle_and_name(self, line):
2.109 + match = self._name_pattern.search(line)
2.110 + if match:
2.111 + return self._get_handle_and_name(line[:match.start()].strip())
2.112 + else:
2.113 + raise OSError, "Window information from %r did not contain window details." % line
2.114 +
2.115 + def _descendants(self, s, fn):
2.116 handles = []
2.117 adding = 0
2.118 for line in s.split("\n"):
2.119 - if not adding and line.endswith("children:"):
2.120 - adding = 1
2.121 + if line.endswith("child:") or line.endswith("children:"):
2.122 + if not adding:
2.123 + adding = 1
2.124 elif adding and line:
2.125 - handles.append(line.strip().split()[0])
2.126 + handle, name = self._get_descendant_handle_and_name(line)
2.127 + if fn(name):
2.128 + handles.append(handle)
2.129 return [Window(handle) for handle in handles]
2.130
2.131 + # Public methods.
2.132 +
2.133 + def children(self, all=0):
2.134 +
2.135 + """
2.136 + Return a list of windows which are children of this window. If the
2.137 + optional 'all' parameter is set to a true value, all such windows will
2.138 + be returned regardless of whether they have any name information.
2.139 + """
2.140 +
2.141 + s = self._get_command_output("children")
2.142 + return self._descendants(s, all and self.find_all or self.find_named)
2.143 +
2.144 + def descendants(self, all=0):
2.145 +
2.146 + """
2.147 + Return a list of windows which are descendants of this window. If the
2.148 + optional 'all' parameter is set to a true value, all such windows will
2.149 + be returned regardless of whether they have any name information.
2.150 + """
2.151 +
2.152 + s = self._get_command_output("tree")
2.153 + return self._descendants(s, all and self.find_all or self.find_named)
2.154 +
2.155 + def find(self, callable):
2.156 +
2.157 + """
2.158 + Return windows using the given 'callable' (returning a true or a false
2.159 + value when invoked with a window name) for descendants of this window.
2.160 + """
2.161 +
2.162 + s = self._get_command_output("tree")
2.163 + return self._descendants(s, callable)
2.164 +
2.165 def name(self):
2.166
2.167 "Return the name of the window."
2.168
2.169 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
2.170 + s = self._get_command_output("stats")
2.171 for line in s.split("\n"):
2.172 if line.startswith("xwininfo:"):
2.173
2.174 # Format is 'xwininfo: Window id: <handle> "<name>"
2.175
2.176 - fields = line.split(":")
2.177 - handle_and_name = fields[2].strip()
2.178 - fields2 = handle_and_name.split(" ")
2.179 -
2.180 - # Get the "<name>" part, stripping off the quotes.
2.181 -
2.182 - return " ".join(fields2[1:]).strip('"')
2.183 + return self._get_this_handle_and_name(line)[1]
2.184
2.185 return None
2.186
2.187 @@ -121,7 +201,7 @@
2.188
2.189 "Return a tuple containing the width and height of this window."
2.190
2.191 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
2.192 + s = self._get_command_output("stats")
2.193 d = _xwininfo(s)
2.194 return _get_int_properties(d, ["Width", "Height"])
2.195
2.196 @@ -129,10 +209,18 @@
2.197
2.198 "Return a tuple containing the upper left co-ordinates of this window."
2.199
2.200 - s = _readfrom(_get_x11_vars() + "xwininfo %s -stats" % self._get_identifier(), shell=1)
2.201 + s = self._get_command_output("stats")
2.202 d = _xwininfo(s)
2.203 return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
2.204
2.205 + def visible(self):
2.206 +
2.207 + "Return whether the window is in some way visible."
2.208 +
2.209 + s = self._get_command_output("stats")
2.210 + d = _xwininfo(s)
2.211 + return d["Map State"] != "IsUnviewable"
2.212 +
2.213 def list(desktop=None):
2.214
2.215 """
2.216 @@ -141,25 +229,10 @@
2.217 environment's mechanisms to look for windows.
2.218 """
2.219
2.220 - # NOTE: The desktop parameter is currently ignored and X11 is tested for
2.221 - # NOTE: directly.
2.222 -
2.223 - if _is_x11():
2.224 - s = _readfrom(_get_x11_vars() + "xlsclients -a -l", shell=1)
2.225 - prefix = "Window "
2.226 - prefix_end = len(prefix)
2.227 -
2.228 - # Include the root window.
2.229 -
2.230 - handles = [None]
2.231 -
2.232 - for line in s.split("\n"):
2.233 - if line.startswith(prefix):
2.234 - handles.append(line[prefix_end:-1]) # NOTE: Assume ":" at end.
2.235 - else:
2.236 - raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
2.237 -
2.238 - return [Window(handle) for handle in handles]
2.239 + root_window = root(desktop)
2.240 + window_list = [window for window in root_window.descendants() if window.visible()]
2.241 + window_list.insert(0, root_window)
2.242 + return window_list
2.243
2.244 def root(desktop=None):
2.245
2.246 @@ -177,4 +250,14 @@
2.247 else:
2.248 raise OSError, "Desktop '%s' not supported" % use_desktop(desktop)
2.249
2.250 +def find(callable, desktop=None):
2.251 +
2.252 + """
2.253 + Find and return windows using the given 'callable' for the current desktop.
2.254 + If the optional 'desktop' parameter is specified then attempt to use that
2.255 + particular desktop environment's mechanisms to look for windows.
2.256 + """
2.257 +
2.258 + return root(desktop).find(callable)
2.259 +
2.260 # vim: tabstop=4 expandtab shiftwidth=4