← Back to team overview

xpresser-team team mailing list archive

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

 

Sylvain Pineau has proposed merging lp:~sylvain-pineau/xpresser/gtk3-port-with-cairo into lp:xpresser.

Requested reviews:
  Xpresser Team (xpresser-team)

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

This MR includes the GTK3 port started by Gustavo and the port to the new OpenCV 2.3 python wrapper cv2.
Unfortunately I've neglected to update the tests. 
-- 
https://code.launchpad.net/~sylvain-pineau/xpresser/gtk3-port-with-cairo/+merge/100615
Your team Xpresser Team is requested to review the proposed merge of lp:~sylvain-pineau/xpresser/gtk3-port-with-cairo into lp:xpresser.
=== added directory 'debian'
=== added file 'debian/changelog'
--- debian/changelog	1970-01-01 00:00:00 +0000
+++ debian/changelog	2012-04-03 14:48:19 +0000
@@ -0,0 +1,78 @@
+xpresser (1.0-1ubuntu12) precise; urgency=low
+
+  * GTK3 port
+  * OpenCV 2.3 port (cv2 python wrapper)
+
+ -- Sylvain Pineau <sylvain.pineau@xxxxxxxxxxxxx>  Tue, 03 Apr 2012 16:20:16 +0200
+
+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-04-03 14:48:19 +0000
@@ -0,0 +1,1 @@
+7

=== added file 'debian/control'
--- debian/control	1970-01-01 00:00:00 +0000
+++ debian/control	2012-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +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-04-03 14:48:19 +0000
@@ -19,14 +19,14 @@
 #
 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 +35,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-04-03 14:48:19 +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-04-03 14:48:19 +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/opencvfinder.py'
--- xpresser/opencvfinder.py	2010-05-18 14:38:17 +0000
+++ xpresser/opencvfinder.py	2012-04-03 14:48:19 +0000
@@ -19,13 +19,8 @@
 #
 import math
 import time
-
 import numpy
-
-import opencv
-import opencv.highgui
-import opencv.adaptors
-
+import cv2
 from xpresser.imagematch import ImageMatch
 
 
@@ -49,16 +44,14 @@
     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 = cv2.imread(image.filename, 0)
             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
-            image.width = opencv_image.width
-            image.height = opencv_image.height
+            image.width = opencv_image.shape[0]
+            image.height = opencv_image.shape[1]
         return image.cache["opencv_image"]
 
     def _find(self, screen_image, area_image, best_match=False):
@@ -69,17 +62,15 @@
         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)
+        result_width = screen.shape[0] - area.shape[0] + 1
+        result_height = screen.shape[1] - area.shape[1] + 1
         if DEBUG_PERFORMANCE:
             started = time.time()
-        opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
+        result = cv2.matchTemplate(screen, area, cv2.TM_CCORR_NORMED)
         if DEBUG_PERFORMANCE:
             print "MATCHING: %.5fs" % (time.time()-started)
 
-        result = opencv.adaptors.Ipl2NumPy(result)
+        result = numpy.asarray( result[:,:] )
 
         if DEBUG_PERFORMANCE:
             started = time.time()

=== 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-04-03 14:48:19 +0000
@@ -18,7 +18,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 import opencv
-import gtk
+
+from gi.repository import Gtk as gtk
 
 from xpresser.image import Image
 from xpresser.opencvfinder import OpenCVFinder
@@ -138,7 +139,7 @@
 
         # 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()
+        pixbuf = gtk.Image.new_from_file(filename).get_pixbuf()
         self.green_square.array = pixbuf.get_pixels_array()
 
         # Try to match normally.

=== 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-04-03 14:48:19 +0000
@@ -20,7 +20,7 @@
 import threading
 import time
 
-import gtk
+from gi.repository import Gtk
 
 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()
@@ -74,9 +74,9 @@
         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._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

=== 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-04-03 14:48:19 +0000
@@ -19,7 +19,7 @@
 #
 import time
 
-import gtk
+from gi.repository import Gtk
 
 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,21 @@
         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))
+        button.set_image(Gtk.image_new_from_file(image_path))
         return self.create_window(button)
 
     def create_image_window(self, image_path):
-        image = gtk.image_new_from_file(image_path)
+        image = Gtk.image_new_from_file(image_path)
         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,7 +95,7 @@
 
         self.flush_gtk()
 
-        resolution = gtk.gdk.get_default_root_window().get_size()
+        resolution = 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()
@@ -153,9 +153,9 @@
         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._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

=== modified file 'xpresser/xutils.py'
--- xpresser/xutils.py	2011-12-09 18:35:34 +0000
+++ xpresser/xutils.py	2012-04-03 14:48:19 +0000
@@ -20,12 +20,10 @@
 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.*")
+import numpy
+import cv2
+from tempfile import NamedTemporaryFile
+from gi.repository import Gdk
 
 
 def click(x, y):
@@ -42,21 +40,20 @@
 
 def type(string):
     for char in string:
-        keyval = gtk.gdk.unicode_to_keyval(ord(char))
+        keyval = 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()
+    window = Gdk.get_default_root_window()
     if not (width and height):
-        size = window.get_size()
         if not width:
-            width = size[0]
+            width = window.get_width()
         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])
+            height = window.get_height()
+    surface = Gdk.cairo_create(window).get_target()
+    with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f:
+        surface.write_to_png(f.name)
+        opencv_image = cv2.imread(f.name, 0)
+    return Image("screenshot", array=opencv_image,
+                 width=width, height=height)


Follow ups