xpresser-team team mailing list archive
-
xpresser-team team
-
Mailing list archive
-
Message #00007
[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