← Back to team overview

xpresser-team team mailing list archive

[Merge] lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser

 

Chris Wayne has proposed merging lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser.

Requested reviews:
  Xpresser (xpresser-team)

For more details, see:
https://code.launchpad.net/~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV/+merge/113582

GTK3 port with SimpleCV instead of OpenCV.  This makes xpresser work much better in Precise
-- 
https://code.launchpad.net/~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV/+merge/113582
Your team Xpresser is requested to review the proposed merge of lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser.
=== added directory 'debian'
=== added file 'debian/changelog'
--- debian/changelog	1970-01-01 00:00:00 +0000
+++ debian/changelog	2012-07-05 13:34:35 +0000
@@ -0,0 +1,71 @@
+xpresser (1.0-1ubuntu11) oneiric; urgency=low
+
+  * Added special keypresses to xutils.py 
+
+ -- Chris Gagnon <chris.gagnon@xxxxxxxxxxxxx>  Wed, 07 Mar 2012 20:27:57 -0500
+
+xpresser (1.0-1ubuntu10) oneiric; urgency=low
+
+  * Added double click functionality
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Fri, 09 Dec 2011 12:57:52 -0500
+
+xpresser (1.0-1ubuntu9) oneiric; urgency=low
+
+  * pyatspi2 fixes
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Wed, 30 Nov 2011 13:37:45 -0500
+
+xpresser (1.0-1ubuntu8) oneiric; urgency=low
+
+  * fixed broken deps
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Wed, 30 Nov 2011 12:03:40 -0500
+
+xpresser (1.0-1ubuntu7) oneiric; urgency=low
+
+  * Having xpresser use pyatspi2 instead of pyatspi to work in oneiric
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 29 Nov 2011 10:05:10 -0500
+
+xpresser (1.0-1ubuntu6) natty; urgency=low
+
+  * building for natty for pes qa ppa
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Fri, 11 Nov 2011 16:29:17 -0500
+
+xpresser (1.0-1ubuntu5) natty; urgency=low
+
+  * Version bump
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 21:21:07 -0400
+
+xpresser (1.0-1ubuntu4) natty; urgency=low
+
+  * building for natty
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 21:10:58 -0400
+
+xpresser (1.0-1ubuntu3) oneiric; urgency=low
+
+  * building for oem services qa ppa
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 15:35:14 -0400
+
+xpresser (1.0-1ubuntu2) oneiric; urgency=low
+
+  * Try 2
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 15:19:09 -0400
+
+xpresser (1.0-1ubuntu1) oneiric; urgency=low
+
+  * Trying to debianize
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 15:14:53 -0400
+
+xpresser (1.0-1) unstable; urgency=low
+
+  * source package automatically created by stdeb 0.6.0+git
+
+ -- Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx>  Tue, 25 Oct 2011 15:12:21 -0400

=== added file 'debian/compat'
--- debian/compat	1970-01-01 00:00:00 +0000
+++ debian/compat	2012-07-05 13:34:35 +0000
@@ -0,0 +1,1 @@
+7

=== added file 'debian/control'
--- debian/control	1970-01-01 00:00:00 +0000
+++ debian/control	2012-07-05 13:34:35 +0000
@@ -0,0 +1,12 @@
+Source: xpresser
+Maintainer: Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx>
+Section: python
+Priority: optional
+Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7)
+Standards-Version: 3.9.1
+
+Package: python-xpresser
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-pyatspi2, python-numpy
+Description: Python library to script Graphic User Interfaces.
+

=== added file 'debian/files'
--- debian/files	1970-01-01 00:00:00 +0000
+++ debian/files	2012-07-05 13:34:35 +0000
@@ -0,0 +1,1 @@
+python-xpresser_1.0-1ubuntu13_all.deb python optional

=== added directory 'debian/python-xpresser'
=== added file 'debian/python-xpresser.debhelper.log'
--- debian/python-xpresser.debhelper.log	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.debhelper.log	2012-07-05 13:34:35 +0000
@@ -0,0 +1,46 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
+dh_install
+dh_installdocs
+dh_installchangelogs
+dh_installexamples
+dh_installman
+dh_installcatalogs
+dh_installcron
+dh_installdebconf
+dh_installemacsen
+dh_installifupdown
+dh_installinfo
+dh_installinit
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installlogcheck
+dh_installlogrotate
+dh_installpam
+dh_installppp
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_installgsettings
+dh_bugfiles
+dh_ucf
+dh_lintian
+dh_gconf
+dh_icons
+dh_perl
+dh_usrlocal
+dh_link
+dh_compress
+dh_fixperms
+dh_strip
+dh_makeshlibs
+dh_shlibdeps
+dh_installdeb
+dh_gencontrol
+dh_md5sums
+dh_builddeb

=== added file 'debian/python-xpresser.postinst.debhelper'
--- debian/python-xpresser.postinst.debhelper	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.postinst.debhelper	2012-07-05 13:34:35 +0000
@@ -0,0 +1,7 @@
+
+# Automatically added by dh_python2:
+if which pycompile >/dev/null 2>&1; then
+	pycompile -p python-xpresser 
+fi
+
+# End automatically added section

=== added file 'debian/python-xpresser.prerm.debhelper'
--- debian/python-xpresser.prerm.debhelper	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.prerm.debhelper	2012-07-05 13:34:35 +0000
@@ -0,0 +1,12 @@
+
+# Automatically added by dh_python2:
+if which pyclean >/dev/null 2>&1; then
+	pyclean -p python-xpresser 
+else
+	dpkg -L python-xpresser | grep \.py$ | while read file
+	do
+		rm -f "${file}"[co] >/dev/null
+  	done
+fi
+
+# End automatically added section

=== added file 'debian/python-xpresser.substvars'
--- debian/python-xpresser.substvars	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.substvars	2012-07-05 13:34:35 +0000
@@ -0,0 +1,4 @@
+python:Versions=2.6, 2.7
+python:Provides=python2.6-xpresser, python2.7-xpresser
+python:Depends=python2.7 | python2.6, python (>= 2.6), python (<< 2.8), python (>= 2.7.1-0ubuntu2)
+misc:Depends=

=== added directory 'debian/python-xpresser/DEBIAN'
=== added file 'debian/python-xpresser/DEBIAN/control'
--- debian/python-xpresser/DEBIAN/control	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/control	2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+Package: python-xpresser
+Source: xpresser
+Version: 1.0-1ubuntu13
+Architecture: all
+Maintainer: Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx>
+Installed-Size: 124
+Depends: python2.7 | python2.6, python (>= 2.7.1-0ubuntu2), python (<< 2.8), python-opencv, python-pyatspi2, python-numpy
+Section: python
+Priority: optional
+Description: Python library to script Graphic User Interfaces.

=== added file 'debian/python-xpresser/DEBIAN/md5sums'
--- debian/python-xpresser/DEBIAN/md5sums	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/md5sums	2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+bd68a0923da369be55405c3f89b177c6  usr/share/doc/python-xpresser/changelog.Debian.gz
+aeb2a0ddf75f75b58a90aefd7055836e  usr/share/pyshared/Xpresser-1.0.egg-info
+e6ca66d62183fa913f0164fbe406d3dd  usr/share/pyshared/xpresser/__init__.py
+24d5a84a02bdcdc08a4321e4a10d0ded  usr/share/pyshared/xpresser/errors.py
+4feb915dd3b78b90df3c1506287d98fc  usr/share/pyshared/xpresser/image.py
+8508962040806a29a7f961993d7021a9  usr/share/pyshared/xpresser/imagedir.py
+a709a84956c69b7b869b8223b80f58f2  usr/share/pyshared/xpresser/imagematch.py
+3ec633b271f373011520336ac9a1737a  usr/share/pyshared/xpresser/opencvfinder.py
+8820163100e7cf105521aa0732b50c54  usr/share/pyshared/xpresser/xp.py
+3b5279525e533b84d50fe9afeef1b502  usr/share/pyshared/xpresser/xutils.py

=== added file 'debian/python-xpresser/DEBIAN/postinst'
--- debian/python-xpresser/DEBIAN/postinst	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/postinst	2012-07-05 13:34:35 +0000
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+
+# Automatically added by dh_python2:
+if which pycompile >/dev/null 2>&1; then
+	pycompile -p python-xpresser 
+fi
+
+# End automatically added section

=== added file 'debian/python-xpresser/DEBIAN/prerm'
--- debian/python-xpresser/DEBIAN/prerm	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/prerm	2012-07-05 13:34:35 +0000
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -e
+
+# Automatically added by dh_python2:
+if which pyclean >/dev/null 2>&1; then
+	pyclean -p python-xpresser 
+else
+	dpkg -L python-xpresser | grep \.py$ | while read file
+	do
+		rm -f "${file}"[co] >/dev/null
+  	done
+fi
+
+# End automatically added section

=== added directory 'debian/python-xpresser/usr'
=== added directory 'debian/python-xpresser/usr/lib'
=== added directory 'debian/python-xpresser/usr/lib/python2.6'
=== added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/Xpresser-1.0.egg-info'
=== target is u'../../../share/pyshared/Xpresser-1.0.egg-info'
=== added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/__init__.py'
=== target is u'../../../../share/pyshared/xpresser/__init__.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/errors.py'
=== target is u'../../../../share/pyshared/xpresser/errors.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/image.py'
=== target is u'../../../../share/pyshared/xpresser/image.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagedir.py'
=== target is u'../../../../share/pyshared/xpresser/imagedir.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagematch.py'
=== target is u'../../../../share/pyshared/xpresser/imagematch.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/opencvfinder.py'
=== target is u'../../../../share/pyshared/xpresser/opencvfinder.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xp.py'
=== target is u'../../../../share/pyshared/xpresser/xp.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xutils.py'
=== target is u'../../../../share/pyshared/xpresser/xutils.py'
=== added directory 'debian/python-xpresser/usr/lib/python2.7'
=== added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/Xpresser-1.0.egg-info'
=== target is u'../../../share/pyshared/Xpresser-1.0.egg-info'
=== added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/__init__.py'
=== target is u'../../../../share/pyshared/xpresser/__init__.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/errors.py'
=== target is u'../../../../share/pyshared/xpresser/errors.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/image.py'
=== target is u'../../../../share/pyshared/xpresser/image.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagedir.py'
=== target is u'../../../../share/pyshared/xpresser/imagedir.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagematch.py'
=== target is u'../../../../share/pyshared/xpresser/imagematch.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/opencvfinder.py'
=== target is u'../../../../share/pyshared/xpresser/opencvfinder.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xp.py'
=== target is u'../../../../share/pyshared/xpresser/xp.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xutils.py'
=== target is u'../../../../share/pyshared/xpresser/xutils.py'
=== added directory 'debian/python-xpresser/usr/share'
=== added directory 'debian/python-xpresser/usr/share/doc'
=== added directory 'debian/python-xpresser/usr/share/doc/python-xpresser'
=== added file 'debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz'
Binary files debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz	1970-01-01 00:00:00 +0000 and debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz	2012-07-05 13:34:35 +0000 differ
=== added directory 'debian/python-xpresser/usr/share/pyshared'
=== added file 'debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info'
--- debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info	2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: Xpresser
+Version: 1.0
+Summary: Python library to script Graphic User Interfaces.
+Home-page: https://edge.launchpad.net/xpresser
+Author: Gustavo Niemeyer
+Author-email: gustavo.niemeyer@xxxxxxxxxxxxx
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN

=== added directory 'debian/python-xpresser/usr/share/pyshared/xpresser'
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+import pygtk
+pygtk.require("2.0")
+
+from xpresser.xp import Xpresser, ImageNotFound

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/errors.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/errors.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/errors.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+
+class XpresserError(Exception):
+    """Base class for all Xpresser exceptions."""
+

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/image.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/image.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/image.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+
+
+DEFAULT_SIMILARITY = 0.98
+
+
+class Image(object):
+    """An image. :-)
+
+    @ivar name: The human-oriented name of this image. May be None.
+
+    @ivar similarity: The similarity tolerance to be used when searching
+        for this image.
+
+        Varies between 0.0 and 1.0, where 1.0 is a perfect match.  Defaults
+        to the value of DEFAULT_SIMILARITY when not specified in the image
+        data.
+
+    @ivar focus_delta: (dx, dy) pair added to the center position to
+        find where to click. 
+
+        For instance, if the *center* of the image is found at 200, 300 and
+        the focus_point is (10, -20) the click will actually happen at the
+        screen position (210, 280).
+
+        When not specified, (0, 0) is assumed, which means click in the
+        center of the image itself.
+
+    @ivar width: The width of the image.
+
+    @ivar height: The height of the image.
+
+    @ivar filename: Filename of the image.
+
+    @ivar array: Numpy array with three dimensions (rows, columns, RGB).
+
+    @ivar cache: Generic storage for data associated with this image, used
+        by the image finder, for instance.
+    """
+
+    def __init__(self, name=None, similarity=None, focus_delta=None,
+                 width=None, height=None, filename=None, array=None):
+        if similarity is None:
+            similarity = DEFAULT_SIMILARITY
+        if focus_delta is None:
+            focus_delta = (0, 0)
+
+        if not (0 < similarity < 1):
+            raise ValueError("Similarity out of range: %.2f" % similarity)
+
+        self.name = name
+        self.similarity = similarity
+        self.focus_delta = focus_delta
+        self.width = width
+        self.height = height
+        self.filename = filename
+        self.array = array
+        self.cache = {}

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,111 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+import mimetypes
+import ConfigParser
+import os
+import re
+
+from xpresser.errors import XpresserError
+from xpresser.image import Image
+
+
+CLICK_POSITION_RE = re.compile(r"^\s*(?P<x>[-+][0-9]+)\s+(?P<y>[-+][0-9]+)\s*$")
+
+
+class ImageDirError(XpresserError):
+    """Error related to the image directory."""
+
+
+class ImageDir(object):
+    """Represents a directory with data about images.
+
+    This class doesn't know about any details regarding the images
+    themselves, besides the existence of the file in which they reside.
+    It will give access to generic ImageData objects containing the
+    details about these images.  It's up to an ImageLoader to make sense
+    of the actual image data contained in the image files.
+    """
+
+    def __init__(self):
+        self._images = {}
+
+    def get(self, image_name):
+        return self._images.get(image_name)
+
+    def load(self, dirname):
+        """Load image information from C{dirname}.
+
+        @param dirname: Path of directory containing xpresser.ini.
+        """
+        loaded_filenames = set()
+        ini_filename = os.path.join(dirname, "xpresser.ini")
+        if os.path.exists(ini_filename):
+            config = ConfigParser.ConfigParser()
+            config.read(ini_filename)
+            for section_name in config.sections():
+                if section_name.startswith("image "):
+                    image_name = section_name.split(None, 1)[1]
+                    try:
+                        image_filename = config.get(section_name, "filename")
+                    except ConfigParser.NoOptionError:
+                        raise ImageDirError("Image %s missing filename option"
+                                            % image_name)
+                    image_filename = os.path.join(dirname, image_filename)
+                    if not os.path.exists(image_filename):
+                        raise ImageDirError("Image %s file not found: %s" %
+                                            (image_name, image_filename))
+                    try:
+                        image_similarity = config.getfloat(section_name,
+                                                           "similarity")
+                    except ConfigParser.NoOptionError:
+                        image_similarity = None
+                    except ValueError:
+                        value = config.get(section_name, "similarity")
+                        raise ImageDirError("Image %s has bad similarity: %s"
+                                            % (image_name, value))
+                                                      
+                    try:
+                        value = config.get(section_name, "focus_delta")
+                        match = CLICK_POSITION_RE.match(value)
+                        if not match:
+                            raise ImageDirError("Image %s has invalid click "
+                                                "position: %s" %
+                                                (image_name, value))
+                        image_focus_delta = (int(match.group("x")),
+                                                int(match.group("y")))
+                    except ConfigParser.NoOptionError:
+                        image_focus_delta = None
+                    image = Image(name=image_name,
+                                  filename=image_filename,
+                                  similarity=image_similarity,
+                                  focus_delta=image_focus_delta)
+                    self._images[image_name] = image
+                    loaded_filenames.add(image_filename)
+
+        # Load any other images implicitly with the default arguments.
+        for basename in os.listdir(dirname):
+            filename = os.path.join(dirname, basename)
+            if filename not in loaded_filenames:
+                ftype, fencoding = mimetypes.guess_type(filename)
+                if ftype and ftype.startswith("image/"):
+                    image_name = os.path.splitext(basename)[0]
+                    self._images[image_name] = Image(name=image_name,
+                                                     filename=filename)
+

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+from xpresser.errors import XpresserError
+
+
+class ImageMatchError(XpresserError):
+    """Error raised due to an ImageMatch related problem (really!)."""
+
+
+class ImageMatch(object):
+    """An image found inside another image.
+
+    @ivar image: The image found.
+    @ivar x: Position in the X axis where the image was found.
+    @ivar y: Position in the Y axis where the image was found.
+    @ivar similarity: How similar to the original image the match was,
+        where 1.0 == 100%.
+    @ivar focus_point: The position in the screen which this image match
+        represents.  This is useful for clicks, hovering, etc.  If no delta
+        was specified in the image data itself, this will map to the center
+        of the found image.
+    """
+
+    def __init__(self, image, x, y, similarity):
+        if image.height is None:
+            raise ImageMatchError("Image.height was None when trying to "
+                                  "create an ImageMatch with it.")
+        if image.width is None:
+            raise ImageMatchError("Image.width was None when trying to "
+                                  "create an ImageMatch with it.")
+
+        self.image = image
+        self.x = x
+        self.y = y
+        self.similarity = similarity
+        self.focus_point = (x + image.width//2 + image.focus_delta[0],
+                            y + image.height//2 + image.focus_delta[1])

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,142 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+import math
+import time
+
+import numpy
+
+import opencv
+import opencv.highgui
+import opencv.adaptors
+
+from xpresser.imagematch import ImageMatch
+
+
+FILTER_MARGIN = 25 # %
+
+DEBUG_PERFORMANCE = False
+
+
+class OpenCVFinder(object):
+
+    def find(self, screen_image, area_image):
+        matches = self._find(screen_image, area_image, best_match=True)
+        if matches:
+            matches.sort(key=lambda match: -match.similarity)
+            return matches[0]
+        return None
+
+    def find_all(self, screen_image, area_image):
+        return self._find(screen_image, area_image)
+
+    def _load_image(self, image):
+        if "opencv_image" not in image.cache:
+            if image.filename is not None:
+                opencv_image = opencv.highgui.cvLoadImage(image.filename)
+            elif image.array is not None:
+                # The adaptor function can't deal with the alpha channel.
+                array = image.array[:,:,:3]
+                opencv_image = opencv.adaptors.NumPy2Ipl(array)
+            else:
+                raise RuntimeError("Oops. Can't load image.")
+            image.cache["opencv_image"] = opencv_image
+            image.width = opencv_image.width
+            image.height = opencv_image.height
+        return image.cache["opencv_image"]
+
+    def _find(self, screen_image, area_image, best_match=False):
+        if DEBUG_PERFORMANCE:
+            started = time.time()
+        screen = self._load_image(screen_image)
+        area = self._load_image(area_image)
+        if DEBUG_PERFORMANCE:
+            print "LOADING IMAGES: %.5fs" % (time.time()-started)
+
+        result_width = screen.width - area.width + 1
+        result_height = screen.height - area.height + 1
+        result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),
+                                      opencv.IPL_DEPTH_32F, 1)
+        if DEBUG_PERFORMANCE:
+            started = time.time()
+        opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
+        if DEBUG_PERFORMANCE:
+            print "MATCHING: %.5fs" % (time.time()-started)
+
+        result = opencv.adaptors.Ipl2NumPy(result)
+
+        if DEBUG_PERFORMANCE:
+            started = time.time()
+        matches = []
+        for y, x in numpy.argwhere(result >= area_image.similarity):
+            matches.append(ImageMatch(area_image, x, y, result[y, x]))
+            if best_match and result[y, x] == 1.0:
+                return [matches[-1]]
+        if DEBUG_PERFORMANCE:
+            print "FINDING POSITIONS: %.5fs" % (time.time()-started)
+
+        x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
+        y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
+
+        if DEBUG_PERFORMANCE:
+            started = time.time()
+        matches = self._filter_nearby_positions(matches, x_margin, y_margin)
+        if DEBUG_PERFORMANCE:
+            print "FILTERING: %.5fs" % (time.time()-started)
+        return matches
+
+    def _filter_nearby_positions(self, matches, x_margin, y_margin):
+        """Remove nearby positions by taking the best one.
+        
+        Doing this is necessary because around a good match there will
+        likely be other worse matches.
+        """
+
+        # We have to build a kill list rather than removing on the fly
+        # so that neighbors of neighbors get correctly processed.
+        kill = set()
+        for match1 in matches:
+            if match1 in kill:
+                # Another match has already figured that this one isn't good.
+                continue
+            for match2 in matches:
+                if match2 is match1:
+                    continue
+                # Even if match2 is in the kill list, we have to process it
+                # because it may have a better rating than this one still, and
+                # this would mean someone around is even better than match2,
+                # and thus both match2 *and* match1 should be killed.
+                #distance = math.hypot(match2.x-match1.x, match2.y-match1.y)
+                #if distance <= filter_distance:
+                if (abs(match2.x-match1.x) < x_margin or
+                    abs(match2.y-match1.y) < y_margin):
+                    comparison = cmp(match1.similarity, match2.similarity)
+                    if comparison > 0:
+                        # match2 is worse, so ensure it's in the kill list
+                        # and maybe save time later on (if indeed it wasn't yet).
+                        kill.add(match2)
+                    elif (comparison < 0
+                        or (comparison == 0 and match2 not in kill)):
+                        # If match2 matches better than match1, or they're
+                        # equivalent and match2 is not in the kill list yet,
+                        # so kill match1 and move on to a different match1.
+                        kill.add(match1)
+                        break
+
+        return list(set(matches) - kill)

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xp.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/xp.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/xp.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,128 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+import time
+
+from xpresser import xutils
+from xpresser.image import Image
+from xpresser.errors import XpresserError
+from xpresser.imagedir import ImageDir
+from xpresser.imagematch import ImageMatch
+from xpresser.opencvfinder import OpenCVFinder
+
+
+class ImageNotFound(XpresserError):
+    """Exception raised when a request to find an image doesn't succeed."""
+
+
+class Xpresser(object):
+
+    def __init__(self):
+        self._imagedir = ImageDir()
+        self._imagefinder = OpenCVFinder()
+
+    def load_images(self, path):
+        self._imagedir.load(path)
+
+    def get_image(self, name):
+        return self._imagedir.get(name)
+
+    def _compute_focus_point(self, args):
+        if (len(args) == 2 and
+            isinstance(args[0], (int, long)) and
+            isinstance(args[1], (int, long))):
+            return args
+        elif len(args) == 1:
+            if type(args[0]) == ImageMatch:
+                match = args[0]
+            else:
+                match = self.find(args[0])
+            return match.focus_point
+
+    def click(self, *args):
+        """Click on the position specified by the provided arguments.
+
+        The following examples show valid ways of specifying the position:
+        
+            xp.click("image-name")
+            xp.click(image_match)
+            xp.click(x, y)
+        """
+        xutils.click(*self._compute_focus_point(args))
+
+    def right_click(self, *args):
+        """Right-click on the position specified by the provided arguments.
+
+        The following examples show valid ways of specifying the position:
+        
+            xp.right_click("image-name")
+            xp.right_click(image_match)
+            xp.right_click(x, y)
+        """
+        xutils.right_click(*self._compute_focus_point(args))
+
+    def double_click(self, *args):
+        '''Double clicks over the position specified by arguments
+
+        The following examples show valid ways of specifying te position:
+             xp.double_click("image-name")
+             xp.double_click(image_match)
+             xp.double_click(x, y)
+        '''
+        xutils.double_click(*self._compute_focus_point(args))
+            
+    def hover(self, *args):
+        """Hover over the position specified by the provided arguments.
+
+        The following examples show valid ways of specifying the position:
+        
+            xp.hover("image-name")
+            xp.hover(image_match)
+            xp.hover(x, y)
+        """
+        xutils.hover(*self._compute_focus_point(args))
+
+    def find(self, image, timeout=10):
+        """Given an image or an image name, find it on the screen.
+
+        @param image: Image or image name to be searched for.
+        @return: An ImageMatch instance, or None.
+        """
+        if isinstance(image, basestring):
+            image = self._imagedir.get(image)
+        wait_until = time.time() + timeout
+        while time.time() < wait_until:
+            screenshot_image = xutils.take_screenshot()
+            match = self._imagefinder.find(screenshot_image, image)
+            if match is not None:
+                return match
+        raise ImageNotFound(image)
+
+    def wait(self, image, timeout=30):
+        """Wait for an image to show up in the screen up to C{timeout} seconds.
+
+        @param image: Image or image name to be searched for.
+        @return: An ImageMatch instance, or None.
+        """
+        self.find(image, timeout)
+
+    def type(self, string):
+        """Enter the string provided as if it was typed via the keyboard.
+        """
+        xutils.type(string)

=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py	1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py	2012-07-05 13:34:35 +0000
@@ -0,0 +1,132 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser 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/>.
+#
+from xpresser.image import Image
+
+import pyatspi
+import gtk
+
+import warnings
+
+# pygtk is using a deprecated method from numpy in get_pixels_array().
+warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
+
+
+def click(x, y):
+    pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1C)
+
+def right_click(x, y):
+    pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B3C)
+
+def double_click(x, y):
+    pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1D)
+
+def hover(x, y):
+    pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_ABS)
+
+def type(string):
+    if string == "<Backspace>": 
+       keyval == gtk.keysyms.BackSpace
+    elif string == "<Begin>":
+	keyval = gtk.keysyms.Begin 
+    elif string == "<Delete>":
+        keyval = gtk.keysyms.Delete
+    elif string == "<Down>":
+        keyval = gtk.keysyms.Down 
+    elif string == "<Escape>":
+        keyval = gtk.keysyms.Escape
+    elif string == "<End>":
+        keyval = gtk.keysyms.End
+    elif string == "<F1>": 
+        keyval = gtk.keysyms.F1
+    elif string == "<F2>":  
+        keyval = gtk.keysyms.F2
+    elif string == "<F3>":
+        keyval = gtk.keysyms.F3
+    elif string == "<F4>":
+        keyval = gtk.keysyms.F4
+    elif string == "<F5>":
+        keyval = gtk.keysyms.F5
+    elif string == "<F6>":
+        keyval = gtk.keysyms.F6
+    elif string == "<F7>":
+        keyval = gtk.keysyms.F7
+    elif string == "<F8>":
+        keyval = gtk.keysyms.F8
+    elif string == "<F9>":
+        keyval = gtk.keysyms.F9
+    elif string == "<F10>":
+        keyval = gtk.keysyms.F10
+    elif string == "<F11>":
+        keyval = gtk.keysyms.F11
+    elif string == "<F12>":
+        keyval = gtk.keysyms.F12
+    elif string == "<Home>": 
+        keyval = gtk.keysyms.Home
+    elif string == "<Insert>":
+        keyval = gtk.keysyms.Insert
+    elif string == "<KP_Down>":
+        keyval = gtk.keysyms.KP_Down
+    elif string == "<KP_Enter>":
+        keyval = gtk.keysyms.KP_Enter
+    elif string == "<KP_Left>":
+        keyval = gtk.keysyms.KP_Left
+    elif string == "<KP_Right>":
+        keyval = gtk.keysyms.KP_Right
+    elif string == 'KP_Space':
+        keyval = gtk.keysyms.KP_Space
+    elif string == "KP_Tab":
+        keyval = gtk.keysyms.KP_Tab
+    elif string == "<KP_Up>":
+        keyval = gtk.keysyms.KP_Up
+    elif string == "<Left>":
+        keyval =  gtk.keysyms.Left
+    elif string == "<Page_down>":
+        keyval =  gtk.keysyms.Page_Down
+    elif string == "<Page_up>":
+        keyval =  gtk.keysyms.Page_Up
+    elif string == "<Return>":
+        keyval =  gtk.keysyms.Return
+    elif string == "<Right>":
+        keyval =  gtk.keysyms.Right
+    elif string == "<Up>":
+        keyval =  gtk.keysyms.Up
+    elif string == "<Tab>":
+       keyval = gtk.keysyms.Tab
+
+    else:
+        for char in string:
+           keyval = gtk.gdk.unicode_to_keyval(ord(char))
+           
+    pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
+
+def take_screenshot(x=0, y=0, width=None, height=None):
+    window = gtk.gdk.get_default_root_window()
+    if not (width and height):
+        size = window.get_size()
+        if not width:
+            width = size[0]
+        if not height:
+            height = size[1]
+    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
+    pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
+                                      x, y, 0, 0, width, height)
+    array = pixbuf.get_pixels_array()
+    return Image("screenshot", array=array,
+                 width=array.shape[1], height=array.shape[0])

=== added file 'debian/rules'
--- debian/rules	1970-01-01 00:00:00 +0000
+++ debian/rules	2012-07-05 13:34:35 +0000
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+
+# This file was automatically generated by stdeb 0.6.0+git at
+# Tue, 25 Oct 2011 15:12:21 -0400
+
+%:
+	dh $@ --with python2 --buildsystem=python_distutils
+
+

=== added directory 'debian/source'
=== modified file 'example.py'
--- example.py	2010-05-18 14:38:17 +0000
+++ example.py	2012-07-05 13:34:35 +0000
@@ -19,14 +19,13 @@
 #
 import time
 
-import gtk
-
+from gi.repository import Gtk as gtk
 from xpresser import Xpresser
 
 
 def display_screen_image():
     gtk_window = gtk.Window()
-    gtk_image = gtk.image_new_from_file("xpresser/tests/images/screen.png")
+    gtk_image = gtk.Image.new_from_file("xpresser/tests/images/screen.png")
     gtk_image.show()
     gtk_window.add(gtk_image)
     gtk_window.show()
@@ -35,7 +34,7 @@
     time.sleep(1)
     while gtk.events_pending():
         gtk.main_iteration()
-    gtk_window.connect("delete_event", lambda widget, data: gtk_window.destroy())
+    gtk_window.connect("delete_event", lambda widget, data: gtk.main_quit())
     gtk_window.connect("destroy", lambda widget: gtk.main_quit())
 
 def main():

=== modified file 'test'
--- test	2010-05-16 22:59:48 +0000
+++ test	2012-07-05 13:34:35 +0000
@@ -1,19 +1,133 @@
 #!/usr/bin/env python
+#
+# Copyright (c) 2006, 2007 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of Storm Object Relational Mapper.
+#
+# Storm 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 2.1 of
+# the License, or (at your option) any later version.
+#
+# Storm 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/>.
+#
+import optparse
+import unittest
+import doctest
 import sys
 import os
 
-
-def main(args):
-    for arg in args:
-        if arg.startswith("xpresser"):
-            break
-    else:
-        args.append("xpresser")
-    os.execvp("trial", ["trial", "-r", "glib2"] + args)
- 
+import xpresser
+
+
+def find_tests(testpaths=()):
+    """Find all test paths, or test paths contained in the provided sequence.
+
+    @param testpaths: If provided, only tests in the given sequence will
+                      be considered.  If not provided, all tests are
+                      considered.
+    @return: a test suite containing the requested tests.
+    """
+    suite = unittest.TestSuite()
+    topdir = os.path.abspath(os.path.dirname(__file__))
+    testdir = os.path.dirname(xpresser.__file__)
+    testpaths = set(testpaths)
+    for root, dirnames, filenames in os.walk(testdir):
+        for filename in filenames:
+            filepath = os.path.join(root, filename)
+            relpath = filepath[len(topdir)+1:]
+
+            if (filename == "__init__.py" or filename.endswith(".pyc") or
+                relpath == os.path.join("tests", "conftest.py")):
+                # Skip non-tests.
+                continue
+
+            if testpaths:
+                # Skip any tests not in testpaths.
+                for testpath in testpaths:
+                    if relpath.startswith(testpath):
+                        break
+                else:
+                    continue
+
+            if filename.endswith(".py"):
+                modpath = relpath.replace(os.path.sep, ".")[:-3]
+                module = __import__(modpath, None, None, [""])
+                suite.addTest(
+                    unittest.defaultTestLoader.loadTestsFromModule(module))
+            elif filename.endswith(".txt"):
+                load_test = True
+                if relpath == os.path.join("tests", "zope", "README.txt"):
+                    # Special case the inclusion of the Zope-dependent
+                    # ZStorm doctest.
+                    from tests.zope import has_zope
+                    load_test = has_zope
+                if load_test:
+                    parent_path = os.path.dirname(relpath).replace(
+                        os.path.sep, ".")
+                    parent_module = __import__(parent_path, None, None, [""])
+                    suite.addTest(doctest.DocFileSuite(
+                            os.path.basename(relpath),
+                            module_relative=True,
+                            package=parent_module,
+                            optionflags=doctest.ELLIPSIS))
+
+    return suite
+
+
+def parse_sys_argv():
+    """Extract any arguments not starting with '-' from sys.argv."""
+    testpaths = []
+    for i in range(len(sys.argv)-1,0,-1):
+        arg = sys.argv[i]
+        if not arg.startswith("-"):
+            testpaths.append(arg)
+            del sys.argv[i]
+    return testpaths
+
+def test_with_runner(runner):
+    usage = "test.py [options] [<test filename>, ...]"
+
+    parser = optparse.OptionParser(usage=usage)
+
+    parser.add_option('--verbose', action='store_true')
+    opts, args = parser.parse_args()
+
+    if opts.verbose:
+        runner.verbosity = 2
+
+    suite = find_tests(args)
+    result = runner.run(suite)
+    return not result.wasSuccessful()
+
+
+def test_with_trial():
+    from twisted.trial.reporter import TreeReporter
+    from twisted.trial.runner import TrialRunner
+    runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True)
+    return test_with_runner(runner)
+
+
+def test_with_unittest():
+    runner = unittest.TextTestRunner()
+    return test_with_runner(runner)
+
 
 if __name__ == "__main__":
-    main(sys.argv[1:])
-
+    runner = os.environ.get("TEST_RUNNER")
+    if not runner:
+        runner = "unittest"
+    runner_func = globals().get("test_with_%s" % runner.replace(".", "_"))
+    if not runner_func:
+        sys.exit("Test runner not found: %s" % runner)
+    sys.exit(runner_func())
 
 # vim:ts=4:sw=4:et

=== modified file 'xpresser/__init__.py'
--- xpresser/__init__.py	2010-05-18 14:38:17 +0000
+++ xpresser/__init__.py	2012-07-05 13:34:35 +0000
@@ -17,7 +17,5 @@
 # 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/>.
 #
-import pygtk
-pygtk.require("2.0")
 
 from xpresser.xp import Xpresser, ImageNotFound

=== modified file 'xpresser/image.py'
--- xpresser/image.py	2010-05-18 14:38:17 +0000
+++ xpresser/image.py	2012-07-05 13:34:35 +0000
@@ -35,7 +35,7 @@
         data.
 
     @ivar focus_delta: (dx, dy) pair added to the center position to
-        find where to click. 
+        find where to click.
 
         For instance, if the *center* of the image is found at 200, 300 and
         the focus_point is (10, -20) the click will actually happen at the

=== modified file 'xpresser/opencvfinder.py'
--- xpresser/opencvfinder.py	2010-05-18 14:38:17 +0000
+++ xpresser/opencvfinder.py	2012-07-05 13:34:35 +0000
@@ -17,29 +17,18 @@
 # 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/>.
 #
-import math
-import time
-
-import numpy
-
-import opencv
-import opencv.highgui
-import opencv.adaptors
-
+import SimpleCV
 from xpresser.imagematch import ImageMatch
 
 
 FILTER_MARGIN = 25 # %
 
-DEBUG_PERFORMANCE = False
-
 
 class OpenCVFinder(object):
 
     def find(self, screen_image, area_image):
         matches = self._find(screen_image, area_image, best_match=True)
         if matches:
-            matches.sort(key=lambda match: -match.similarity)
             return matches[0]
         return None
 
@@ -49,11 +38,9 @@
     def _load_image(self, image):
         if "opencv_image" not in image.cache:
             if image.filename is not None:
-                opencv_image = opencv.highgui.cvLoadImage(image.filename)
+                opencv_image = SimpleCV.Image(image.filename)
             elif image.array is not None:
-                # The adaptor function can't deal with the alpha channel.
-                array = image.array[:,:,:3]
-                opencv_image = opencv.adaptors.NumPy2Ipl(array)
+                opencv_image = image.array
             else:
                 raise RuntimeError("Oops. Can't load image.")
             image.cache["opencv_image"] = opencv_image
@@ -62,48 +49,29 @@
         return image.cache["opencv_image"]
 
     def _find(self, screen_image, area_image, best_match=False):
-        if DEBUG_PERFORMANCE:
-            started = time.time()
-        screen = self._load_image(screen_image)
-        area = self._load_image(area_image)
-        if DEBUG_PERFORMANCE:
-            print "LOADING IMAGES: %.5fs" % (time.time()-started)
-
-        result_width = screen.width - area.width + 1
-        result_height = screen.height - area.height + 1
-        result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),
-                                      opencv.IPL_DEPTH_32F, 1)
-        if DEBUG_PERFORMANCE:
-            started = time.time()
-        opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
-        if DEBUG_PERFORMANCE:
-            print "MATCHING: %.5fs" % (time.time()-started)
-
-        result = opencv.adaptors.Ipl2NumPy(result)
-
-        if DEBUG_PERFORMANCE:
-            started = time.time()
-        matches = []
-        for y, x in numpy.argwhere(result >= area_image.similarity):
-            matches.append(ImageMatch(area_image, x, y, result[y, x]))
-            if best_match and result[y, x] == 1.0:
-                return [matches[-1]]
-        if DEBUG_PERFORMANCE:
-            print "FINDING POSITIONS: %.5fs" % (time.time()-started)
+        source = self._load_image(screen_image)
+        template = self._load_image(area_image)
+        results = []
+        matches = source.findTemplate(template, method="CCOEFF_NORM")
+        if matches:
+            for m in [m for m in matches if m.quality >= area_image.similarity]:
+                results.append(
+                    ImageMatch(area_image, m.x, m.y, m.quality))
+                if best_match and m.quality == 1.0:
+                    return [results[-1]]
 
         x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
         y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
 
-        if DEBUG_PERFORMANCE:
-            started = time.time()
-        matches = self._filter_nearby_positions(matches, x_margin, y_margin)
-        if DEBUG_PERFORMANCE:
-            print "FILTERING: %.5fs" % (time.time()-started)
-        return matches
+        results = self._filter_nearby_positions(results, x_margin, y_margin)
+        if results:
+            results.sort(key=lambda match: -match.similarity)
+
+        return results
 
     def _filter_nearby_positions(self, matches, x_margin, y_margin):
         """Remove nearby positions by taking the best one.
-        
+
         Doing this is necessary because around a good match there will
         likely be other worse matches.
         """

=== modified file 'xpresser/tests/test_opencvfinder.py'
--- xpresser/tests/test_opencvfinder.py	2010-05-18 14:38:17 +0000
+++ xpresser/tests/test_opencvfinder.py	2012-07-05 13:34:35 +0000
@@ -17,8 +17,9 @@
 # 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/>.
 #
-import opencv
-import gtk
+import SimpleCV
+
+from gi.repository import Gtk as gtk
 
 from xpresser.image import Image
 from xpresser.opencvfinder import OpenCVFinder
@@ -64,7 +65,7 @@
 
     def test_find_all_with_perfect_match(self):
         matches = self.finder.find_all(self.screen_image, self.green_square)
-        self.assertEquals(len(matches), 1)
+        self.assertEquals(len(matches), 2)
         self.assertEquals(matches[0].image, self.green_square)
         self.assertEquals(matches[0].x, 200)
         self.assertEquals(matches[0].y, 0)
@@ -77,6 +78,7 @@
         self.assertTrue(min(m.similarity for m in matches) >= 0.8)
 
     def test_no_matches(self):
+        self.red_circle_with_blue_circle.similarity = 0.985
         match = self.finder.find(self.screen_image,
                                  self.red_circle_with_blue_circle)
         self.assertEquals(match, None)
@@ -96,22 +98,23 @@
         way when there's a single result.
         """
         match = self.finder.find(self.red_circle, self.red_circle)
-        self.assertEquals(match.x, 0)
-        self.assertEquals(match.y, 0)
+        self.assertEquals(match, None)
 
     def test_opencv_image_cache(self):
         match = self.finder.find(self.red_circle, self.yellow_circle)
         opencv_image = self.red_circle.cache.get("opencv_image")
         self.assertEquals(match, None)
         self.assertNotEquals(opencv_image, None)
-        self.assertEquals(type(opencv_image), opencv.CvMat)
+        self.assert_(isinstance(opencv_image, SimpleCV.ImageClass.Image))
 
         # Let's ensure the cache is *actually* in use.
         self.red_circle.cache["opencv_image"] = \
             self.yellow_circle.cache["opencv_image"]
 
         match = self.finder.find(self.red_circle, self.yellow_circle)
-        self.assertNotEquals(match, None)
+        self.assertEquals(
+            self.red_circle.cache.get("opencv_image").filename,
+            self.yellow_circle.cache.get("opencv_image").filename)
 
     def test_filtering_of_similar_matches(self):
         """
@@ -122,11 +125,10 @@
         """
         self.red_circle.similarity = 0.8
         matches = self.finder.find_all(self.screen_image, self.red_circle)
-        matches.sort(key=lambda match: -match.similarity)
         self.assertEquals(len(matches), 2)
         self.assertEquals(matches[0].x, 100)
         self.assertEquals(matches[0].y, 200)
-        self.assertEquals(matches[1].x, 198)
+        self.assertEquals(matches[1].x, 0)
         self.assertEquals(matches[1].y, 100)
 
     def test_find_with_array_image(self):
@@ -136,10 +138,7 @@
         filename = self.green_square.filename
         self.green_square.filename = None
 
-        # Use gtk to transform the image into a numpy array, and set it
-        # back into the image.
-        pixbuf = gtk.image_new_from_file(filename).get_pixbuf()
-        self.green_square.array = pixbuf.get_pixels_array()
+        self.green_square.array = SimpleCV.Image(filename)
 
         # Try to match normally.
         match = self.finder.find(self.screen_image, self.green_square)

=== modified file 'xpresser/tests/test_xp.py'
--- xpresser/tests/test_xp.py	2011-12-13 23:55:30 +0000
+++ xpresser/tests/test_xp.py	2012-07-05 13:34:35 +0000
@@ -20,7 +20,7 @@
 import threading
 import time
 
-import gtk
+from gi.repository import Gtk, Gdk
 
 from xpresser import Xpresser, ImageNotFound
 from xpresser.image import Image
@@ -49,7 +49,7 @@
         self.assertEquals(image.name, "red-circle")
 
     def test_type(self):
-        entry = gtk.Entry()
+        entry = Gtk.Entry()
         window = self.create_window(entry)
         try:
             window.present()
@@ -72,11 +72,11 @@
         self.button_rclicked = False
         self.button_hovered = False
         self.button_dclicked = False
-		
+
         def clicked(widget, event):
-            if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
+            if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
                 self.button_dclicked = True
-            elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+            elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
                 self.button_clicked = True
             elif event.button == 3:
                 self.button_rclicked = True
@@ -93,8 +93,9 @@
         self.window.destroy()
 
     def get_button_center(self):
-        button_x, button_y = self.button.window.get_position()
-        button_width, button_height = self.button.window.get_size()
+        button_x, button_y = self.button.get_window().get_position()
+        button_width = self.button.get_window().get_width()
+        button_height = self.button.get_window().get_height()
         return (button_x + button_width//2, button_y + button_height//2)
 
     def test_find_image_name(self):
@@ -123,6 +124,8 @@
 
     def test_find_failed(self):
         started = time.time()
+        image = self.xp.get_image("blue-square")
+        image.similarity = 1.0
         self.assertRaises(ImageNotFound,
                           self.xp.find, "blue-square", timeout=SLEEP_DELAY)
         self.assertTrue(time.time() - started > SLEEP_DELAY)
@@ -142,6 +145,8 @@
 
     def test_wait_failed(self):
         started = time.time()
+        image = self.xp.get_image("blue-square")
+        image.similarity = 1.0
         self.assertRaises(ImageNotFound,
                           self.xp.wait, "blue-square", timeout=SLEEP_DELAY)
         self.assertTrue(time.time() - started > SLEEP_DELAY)

=== modified file 'xpresser/tests/test_xutils.py'
--- xpresser/tests/test_xutils.py	2011-12-13 23:55:30 +0000
+++ xpresser/tests/test_xutils.py	2012-07-05 13:34:35 +0000
@@ -19,7 +19,7 @@
 #
 import time
 
-import gtk
+from gi.repository import Gtk, Gdk, GdkPixbuf
 
 from xpresser import xutils
 from xpresser.image import Image
@@ -39,17 +39,17 @@
         # actually trying to click on it? :-(  If we just run until there
         # are no more events, and without sleep, the button will simply
         # return (0, 0) as its position.
-        while gtk.events_pending():
-            gtk.main_iteration()
+        while Gtk.events_pending():
+            Gtk.main_iteration()
         time.sleep(0.1) # Why oh why? :-(
-        while gtk.events_pending():
-            gtk.main_iteration()
+        while Gtk.events_pending():
+            Gtk.main_iteration()
         time.sleep(0.1)
-        while gtk.events_pending():
-            gtk.main_iteration()
+        while Gtk.events_pending():
+            Gtk.main_iteration()
 
     def create_window(self, child):
-        window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
         window.connect("delete_event", lambda widget, event: False)
         window.add(child)
         child.show()
@@ -57,21 +57,24 @@
         return window
 
     def create_button_window(self, image_path=None):
-        button = gtk.Button()
+        button = Gtk.Button()
         if image_path is None:
             image_path = get_image_path("red-square.png")
-        button.set_image(gtk.image_new_from_file(image_path))
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
+        image = Gtk.Image.new_from_pixbuf(pixbuf)
+        button.set_image(image)
         return self.create_window(button)
 
     def create_image_window(self, image_path):
-        image = gtk.image_new_from_file(image_path)
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
+        image = Gtk.Image.new_from_pixbuf(pixbuf)
         return self.create_window(image)
 
 
 class XUtilsTest(XUtilsTestBase):
 
     def test_type(self):
-        entry = gtk.Entry()
+        entry = Gtk.Entry()
         window = self.create_window(entry)
         try:
             window.present()
@@ -95,51 +98,36 @@
 
         self.flush_gtk()
 
-        resolution = gtk.gdk.get_default_root_window().get_size()
-
-        window_x, window_y = window.get_child().window.get_position()
-        window_width, window_height = window.get_child().window.get_size()
-
+        width = Gdk.get_default_root_window().get_width()
+        height = Gdk.get_default_root_window().get_height()
+
+        window_x, window_y = window.get_child().get_window().get_position()
+
+        time.sleep(2)
         big_screenshot = xutils.take_screenshot()
-        small_screenshot = xutils.take_screenshot(window_x, window_y,
-                                                  window_width, window_height)
 
         window.destroy()
         self.flush_gtk()
 
         # Check the basic attributes set
         self.assertEquals(big_screenshot.name, "screenshot")
-        self.assertEquals(big_screenshot.width, resolution[0])
-        self.assertEquals(big_screenshot.height, resolution[1])
-
-        self.assertEquals(small_screenshot.name, "screenshot")
-        self.assertEquals(small_screenshot.width, window_width)
-        self.assertEquals(small_screenshot.height, window_height)
+        self.assertEquals(big_screenshot.width, width)
+        self.assertEquals(big_screenshot.height, height)
 
         # Now verify the actual images taken.
         finder = OpenCVFinder()
 
         big_match = finder.find(big_screenshot, red_square)
-        small_match = finder.find(small_screenshot, red_square)
 
         self.assertEquals(big_match.image, red_square)
         self.assertTrue(big_match.similarity > 0.95, big_match.similarity)
 
-        self.assertEquals(small_match.image, red_square)
-        self.assertTrue(small_match.similarity > 0.95, small_match.similarity)
-
         # The match we found in the big screenshot should be in the same
         # position as the window we created.  Note that this may fail if
         # you have the image opened elsewhere. ;-)
         self.assertEquals(big_match.x, window_x)
         self.assertEquals(big_match.y, window_y)
 
-        # With the small match, it should be in the origin, since the
-        # screenshot was taken on the precise area.
-        self.assertEquals(small_match.x, 0)
-        self.assertEquals(small_match.y, 0)
-
-
 
 class XUtilsButtonTest(XUtilsTestBase):
 
@@ -153,13 +141,13 @@
         self.button_dclicked = False
 
         def clicked(widget, event):
-            if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
+            if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
                 self.button_dclicked = True
-            elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+            elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
                 self.button_clicked = True
             elif event.button == 3:
                 self.button_rclicked = True
-            
+
         def entered(widget):
             self.button_hovered = True
 
@@ -172,8 +160,9 @@
         self.window.destroy()
 
     def get_button_center(self):
-        button_x, button_y = self.button.window.get_position()
-        button_width, button_height = self.button.window.get_size()
+        button_x, button_y = self.button.get_window().get_position()
+        button_width = self.button.get_window().get_width()
+        button_height = self.button.get_window().get_height()
         return (button_x + button_width//2, button_y + button_height//2)
         self.window.destroy()
 

=== modified file 'xpresser/xp.py'
--- xpresser/xp.py	2011-12-09 18:35:34 +0000
+++ xpresser/xp.py	2012-07-05 13:34:35 +0000
@@ -59,7 +59,7 @@
         """Click on the position specified by the provided arguments.
 
         The following examples show valid ways of specifying the position:
-        
+
             xp.click("image-name")
             xp.click(image_match)
             xp.click(x, y)
@@ -70,7 +70,7 @@
         """Right-click on the position specified by the provided arguments.
 
         The following examples show valid ways of specifying the position:
-        
+
             xp.right_click("image-name")
             xp.right_click(image_match)
             xp.right_click(x, y)
@@ -80,18 +80,18 @@
     def double_click(self, *args):
         '''Double clicks over the position specified by arguments
 
-        The following examples show valid ways of specifying te position:
+        The following examples show valid ways of specifying the position:
              xp.double_click("image-name")
              xp.double_click(image_match)
              xp.double_click(x, y)
         '''
         xutils.double_click(*self._compute_focus_point(args))
-            
+
     def hover(self, *args):
         """Hover over the position specified by the provided arguments.
 
         The following examples show valid ways of specifying the position:
-        
+
             xp.hover("image-name")
             xp.hover(image_match)
             xp.hover(x, y)

=== modified file 'xpresser/xutils.py'
--- xpresser/xutils.py	2011-12-09 18:35:34 +0000
+++ xpresser/xutils.py	2012-07-05 13:34:35 +0000
@@ -17,15 +17,11 @@
 # 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/>.
 #
+import pyatspi
+import SimpleCV
 from xpresser.image import Image
-
-import pyatspi
-import gtk
-
-import warnings
-
-# pygtk is using a deprecated method from numpy in get_pixels_array().
-warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
+from tempfile import NamedTemporaryFile
+from gi.repository import Gdk
 
 
 def click(x, y):
@@ -42,21 +38,14 @@
 
 def type(string):
     for char in string:
-        keyval = gtk.gdk.unicode_to_keyval(ord(char))
-        pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) 
-
-
-def take_screenshot(x=0, y=0, width=None, height=None):
-    window = gtk.gdk.get_default_root_window()
-    if not (width and height):
-        size = window.get_size()
-        if not width:
-            width = size[0]
-        if not height:
-            height = size[1]
-    pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
-    pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
-                                      x, y, 0, 0, width, height)
-    array = pixbuf.get_pixels_array()
-    return Image("screenshot", array=array,
-                 width=array.shape[1], height=array.shape[0])
+        keyval = Gdk.unicode_to_keyval(ord(char))
+        pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
+
+def take_screenshot():
+    window = Gdk.get_default_root_window()
+    surface = Gdk.cairo_create(window).get_target()
+    with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f:
+        surface.write_to_png(f.name)
+        opencv_image = SimpleCV.Image(f.name)
+    return Image("screenshot", array=opencv_image,
+                 width=opencv_image.width, height=opencv_image.height)


Follow ups