274 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| """
 | |
| Simple desktop window enumeration for Python.
 | |
| 
 | |
| Copyright (C) 2007, 2008, 2009 Paul Boddie <paul@boddie.org.uk>
 | |
| 
 | |
| This program is free software; you can redistribute it and/or modify it under
 | |
| the terms of the GNU Lesser General Public License as published by the Free
 | |
| Software Foundation; either version 3 of the License, or (at your option) any
 | |
| later version.
 | |
| 
 | |
| This program is distributed in the hope that it will be useful, but WITHOUT
 | |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
| FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 | |
| details.
 | |
| 
 | |
| You should have received a copy of the GNU Lesser General Public License along
 | |
| with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| --------
 | |
| 
 | |
| Finding Open Windows on the Desktop
 | |
| -----------------------------------
 | |
| 
 | |
| To obtain a list of windows, use the desktop.windows.list function as follows:
 | |
| 
 | |
| windows = desktop.windows.list()
 | |
| 
 | |
| To obtain the root window, typically the desktop background, use the
 | |
| desktop.windows.root function as follows:
 | |
| 
 | |
| root = desktop.windows.root()
 | |
| 
 | |
| Each window object can be inspected through a number of methods. For example:
 | |
| 
 | |
| name = window.name()
 | |
| width, height = window.size()
 | |
| x, y = window.position()
 | |
| child_windows = window.children()
 | |
| 
 | |
| See the desktop.windows.Window class for more information.
 | |
| """
 | |
| 
 | |
| from desktop import _is_x11, _get_x11_vars, _readfrom, use_desktop
 | |
| import re
 | |
| 
 | |
| # System functions.
 | |
| 
 | |
| def _xwininfo(identifier, action):
 | |
|     if identifier is None:
 | |
|         args = "-root"
 | |
|     else:
 | |
|         args = "-id " + identifier
 | |
| 
 | |
|     s = _readfrom(_get_x11_vars() + "xwininfo %s -%s" % (args, action), shell=1)
 | |
| 
 | |
|     # Return a mapping of keys to values for the "stats" action.
 | |
| 
 | |
|     if action == "stats":
 | |
|         d = {}
 | |
|         for line in s.split("\n"):
 | |
|             fields = line.split(":")
 | |
|             if len(fields) < 2:
 | |
|                 continue
 | |
|             key, value = fields[0].strip(), ":".join(fields[1:]).strip()
 | |
|             d[key] = value
 | |
| 
 | |
|         return d
 | |
| 
 | |
|     # Otherwise, return the raw output.
 | |
| 
 | |
|     else:
 | |
|         return s
 | |
| 
 | |
| def _get_int_properties(d, properties):
 | |
|     results = []
 | |
|     for property in properties:
 | |
|         results.append(int(d[property]))
 | |
|     return results
 | |
| 
 | |
| # Finder functions.
 | |
| 
 | |
| def find_all(name):
 | |
|     return 1
 | |
| 
 | |
| def find_named(name):
 | |
|     return name is not None
 | |
| 
 | |
| def find_by_name(name):
 | |
|     return lambda n, t=name: n == t
 | |
| 
 | |
| # Window classes.
 | |
| # NOTE: X11 is the only supported desktop so far.
 | |
| 
 | |
| class Window:
 | |
| 
 | |
|     "A window on the desktop."
 | |
| 
 | |
|     _name_pattern = re.compile(r':\s+\(.*?\)\s+[-0-9x+]+\s+[-0-9+]+$')
 | |
|     _absent_names = "(has no name)", "(the root window) (has no name)"
 | |
| 
 | |
|     def __init__(self, identifier):
 | |
| 
 | |
|         "Initialise the window with the given 'identifier'."
 | |
| 
 | |
|         self.identifier = identifier
 | |
| 
 | |
|         # Finder methods (from above).
 | |
| 
 | |
|         self.find_all = find_all
 | |
|         self.find_named = find_named
 | |
|         self.find_by_name = find_by_name
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return "Window(%r)" % self.identifier
 | |
| 
 | |
|     # Methods which deal with the underlying commands.
 | |
| 
 | |
|     def _get_handle_and_name(self, text):
 | |
|         fields = text.strip().split(" ")
 | |
|         handle = fields[0]
 | |
| 
 | |
|         # Get the "<name>" part, stripping off the quotes.
 | |
| 
 | |
|         name = " ".join(fields[1:])
 | |
|         if len(name) > 1 and name[0] == '"' and name[-1] == '"':
 | |
|             name = name[1:-1]
 | |
| 
 | |
|         if name in self._absent_names:
 | |
|             return handle, None
 | |
|         else:
 | |
|             return handle, name
 | |
| 
 | |
|     def _get_this_handle_and_name(self, line):
 | |
|         fields = line.split(":")
 | |
|         return self._get_handle_and_name(":".join(fields[1:]))
 | |
| 
 | |
|     def _get_descendant_handle_and_name(self, line):
 | |
|         match = self._name_pattern.search(line)
 | |
|         if match:
 | |
|             return self._get_handle_and_name(line[:match.start()].strip())
 | |
|         else:
 | |
|             raise OSError("Window information from %r did not contain window details." % line)
 | |
| 
 | |
|     def _descendants(self, s, fn):
 | |
|         handles = []
 | |
|         adding = 0
 | |
|         for line in s.split("\n"):
 | |
|             if line.endswith("child:") or line.endswith("children:"):
 | |
|                 if not adding:
 | |
|                     adding = 1
 | |
|             elif adding and line:
 | |
|                 handle, name = self._get_descendant_handle_and_name(line)
 | |
|                 if fn(name):
 | |
|                     handles.append(handle)
 | |
|         return [Window(handle) for handle in handles]
 | |
| 
 | |
|     # Public methods.
 | |
| 
 | |
|     def children(self, all=0):
 | |
| 
 | |
|         """
 | |
|         Return a list of windows which are children of this window. If the
 | |
|         optional 'all' parameter is set to a true value, all such windows will
 | |
|         be returned regardless of whether they have any name information.
 | |
|         """
 | |
| 
 | |
|         s = _xwininfo(self.identifier, "children")
 | |
|         return self._descendants(s, all and self.find_all or self.find_named)
 | |
| 
 | |
|     def descendants(self, all=0):
 | |
| 
 | |
|         """
 | |
|         Return a list of windows which are descendants of this window. If the
 | |
|         optional 'all' parameter is set to a true value, all such windows will
 | |
|         be returned regardless of whether they have any name information.
 | |
|         """
 | |
| 
 | |
|         s = _xwininfo(self.identifier, "tree")
 | |
|         return self._descendants(s, all and self.find_all or self.find_named)
 | |
| 
 | |
|     def find(self, callable):
 | |
| 
 | |
|         """
 | |
|         Return windows using the given 'callable' (returning a true or a false
 | |
|         value when invoked with a window name) for descendants of this window.
 | |
|         """
 | |
| 
 | |
|         s = _xwininfo(self.identifier, "tree")
 | |
|         return self._descendants(s, callable)
 | |
| 
 | |
|     def name(self):
 | |
| 
 | |
|         "Return the name of the window."
 | |
| 
 | |
|         d = _xwininfo(self.identifier, "stats")
 | |
| 
 | |
|         # Format is 'xwininfo: Window id: <handle> "<name>"
 | |
| 
 | |
|         return self._get_this_handle_and_name(d["xwininfo"])[1]
 | |
| 
 | |
|     def size(self):
 | |
| 
 | |
|         "Return a tuple containing the width and height of this window."
 | |
| 
 | |
|         d = _xwininfo(self.identifier, "stats")
 | |
|         return _get_int_properties(d, ["Width", "Height"])
 | |
| 
 | |
|     def position(self):
 | |
| 
 | |
|         "Return a tuple containing the upper left co-ordinates of this window."
 | |
| 
 | |
|         d = _xwininfo(self.identifier, "stats")
 | |
|         return _get_int_properties(d, ["Absolute upper-left X", "Absolute upper-left Y"])
 | |
| 
 | |
|     def displayed(self):
 | |
| 
 | |
|         """
 | |
|         Return whether the window is displayed in some way (but not necessarily
 | |
|         visible on the current screen).
 | |
|         """
 | |
| 
 | |
|         d = _xwininfo(self.identifier, "stats")
 | |
|         return d["Map State"] != "IsUnviewable"
 | |
| 
 | |
|     def visible(self):
 | |
| 
 | |
|         "Return whether the window is displayed and visible."
 | |
| 
 | |
|         d = _xwininfo(self.identifier, "stats")
 | |
|         return d["Map State"] == "IsViewable"
 | |
| 
 | |
| def list(desktop=None):
 | |
| 
 | |
|     """
 | |
|     Return a list of windows for the current desktop. If the optional 'desktop'
 | |
|     parameter is specified then attempt to use that particular desktop
 | |
|     environment's mechanisms to look for windows.
 | |
|     """
 | |
| 
 | |
|     root_window = root(desktop)
 | |
|     window_list = [window for window in root_window.descendants() if window.displayed()]
 | |
|     window_list.insert(0, root_window)
 | |
|     return window_list
 | |
| 
 | |
| def root(desktop=None):
 | |
| 
 | |
|     """
 | |
|     Return the root window for the current desktop. If the optional 'desktop'
 | |
|     parameter is specified then attempt to use that particular desktop
 | |
|     environment's mechanisms to look for windows.
 | |
|     """
 | |
| 
 | |
|     # NOTE: The desktop parameter is currently ignored and X11 is tested for
 | |
|     # NOTE: directly.
 | |
| 
 | |
|     if _is_x11():
 | |
|         return Window(None)
 | |
|     else:
 | |
|         raise OSError("Desktop '%s' not supported" % use_desktop(desktop))
 | |
| 
 | |
| def find(callable, desktop=None):
 | |
| 
 | |
|     """
 | |
|     Find and return windows using the given 'callable' for the current desktop.
 | |
|     If the optional 'desktop' parameter is specified then attempt to use that
 | |
|     particular desktop environment's mechanisms to look for windows.
 | |
|     """
 | |
| 
 | |
|     return root(desktop).find(callable)
 | |
| 
 | |
| # vim: tabstop=4 expandtab shiftwidth=4
 |