xpresser-team team mailing list archive
-
xpresser-team team
-
Mailing list archive
-
Message #00011
[Merge] lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser
Chris Wayne has proposed merging lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser.
Requested reviews:
Xpresser (xpresser-team)
For more details, see:
https://code.launchpad.net/~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV/+merge/113582
GTK3 port with SimpleCV instead of OpenCV. This makes xpresser work much better in Precise
--
https://code.launchpad.net/~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV/+merge/113582
Your team Xpresser is requested to review the proposed merge of lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser.
=== added directory 'debian'
=== added file 'debian/changelog'
--- debian/changelog 1970-01-01 00:00:00 +0000
+++ debian/changelog 2012-07-05 13:34:35 +0000
@@ -0,0 +1,71 @@
+xpresser (1.0-1ubuntu11) oneiric; urgency=low
+
+ * Added special keypresses to xutils.py
+
+ -- Chris Gagnon <chris.gagnon@xxxxxxxxxxxxx> Wed, 07 Mar 2012 20:27:57 -0500
+
+xpresser (1.0-1ubuntu10) oneiric; urgency=low
+
+ * Added double click functionality
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Fri, 09 Dec 2011 12:57:52 -0500
+
+xpresser (1.0-1ubuntu9) oneiric; urgency=low
+
+ * pyatspi2 fixes
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Wed, 30 Nov 2011 13:37:45 -0500
+
+xpresser (1.0-1ubuntu8) oneiric; urgency=low
+
+ * fixed broken deps
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Wed, 30 Nov 2011 12:03:40 -0500
+
+xpresser (1.0-1ubuntu7) oneiric; urgency=low
+
+ * Having xpresser use pyatspi2 instead of pyatspi to work in oneiric
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 29 Nov 2011 10:05:10 -0500
+
+xpresser (1.0-1ubuntu6) natty; urgency=low
+
+ * building for natty for pes qa ppa
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Fri, 11 Nov 2011 16:29:17 -0500
+
+xpresser (1.0-1ubuntu5) natty; urgency=low
+
+ * Version bump
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 25 Oct 2011 21:21:07 -0400
+
+xpresser (1.0-1ubuntu4) natty; urgency=low
+
+ * building for natty
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 25 Oct 2011 21:10:58 -0400
+
+xpresser (1.0-1ubuntu3) oneiric; urgency=low
+
+ * building for oem services qa ppa
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 25 Oct 2011 15:35:14 -0400
+
+xpresser (1.0-1ubuntu2) oneiric; urgency=low
+
+ * Try 2
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 25 Oct 2011 15:19:09 -0400
+
+xpresser (1.0-1ubuntu1) oneiric; urgency=low
+
+ * Trying to debianize
+
+ -- Chris Wayne <chris.wayne@xxxxxxxxxxxxx> Tue, 25 Oct 2011 15:14:53 -0400
+
+xpresser (1.0-1) unstable; urgency=low
+
+ * source package automatically created by stdeb 0.6.0+git
+
+ -- Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx> Tue, 25 Oct 2011 15:12:21 -0400
=== added file 'debian/compat'
--- debian/compat 1970-01-01 00:00:00 +0000
+++ debian/compat 2012-07-05 13:34:35 +0000
@@ -0,0 +1,1 @@
+7
=== added file 'debian/control'
--- debian/control 1970-01-01 00:00:00 +0000
+++ debian/control 2012-07-05 13:34:35 +0000
@@ -0,0 +1,12 @@
+Source: xpresser
+Maintainer: Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx>
+Section: python
+Priority: optional
+Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7)
+Standards-Version: 3.9.1
+
+Package: python-xpresser
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-pyatspi2, python-numpy
+Description: Python library to script Graphic User Interfaces.
+
=== added file 'debian/files'
--- debian/files 1970-01-01 00:00:00 +0000
+++ debian/files 2012-07-05 13:34:35 +0000
@@ -0,0 +1,1 @@
+python-xpresser_1.0-1ubuntu13_all.deb python optional
=== added directory 'debian/python-xpresser'
=== added file 'debian/python-xpresser.debhelper.log'
--- debian/python-xpresser.debhelper.log 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.debhelper.log 2012-07-05 13:34:35 +0000
@@ -0,0 +1,46 @@
+dh_auto_configure
+dh_auto_build
+dh_auto_test
+dh_prep
+dh_installdirs
+dh_auto_install
+dh_install
+dh_installdocs
+dh_installchangelogs
+dh_installexamples
+dh_installman
+dh_installcatalogs
+dh_installcron
+dh_installdebconf
+dh_installemacsen
+dh_installifupdown
+dh_installinfo
+dh_installinit
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installlogcheck
+dh_installlogrotate
+dh_installpam
+dh_installppp
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_installgsettings
+dh_bugfiles
+dh_ucf
+dh_lintian
+dh_gconf
+dh_icons
+dh_perl
+dh_usrlocal
+dh_link
+dh_compress
+dh_fixperms
+dh_strip
+dh_makeshlibs
+dh_shlibdeps
+dh_installdeb
+dh_gencontrol
+dh_md5sums
+dh_builddeb
=== added file 'debian/python-xpresser.postinst.debhelper'
--- debian/python-xpresser.postinst.debhelper 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.postinst.debhelper 2012-07-05 13:34:35 +0000
@@ -0,0 +1,7 @@
+
+# Automatically added by dh_python2:
+if which pycompile >/dev/null 2>&1; then
+ pycompile -p python-xpresser
+fi
+
+# End automatically added section
=== added file 'debian/python-xpresser.prerm.debhelper'
--- debian/python-xpresser.prerm.debhelper 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.prerm.debhelper 2012-07-05 13:34:35 +0000
@@ -0,0 +1,12 @@
+
+# Automatically added by dh_python2:
+if which pyclean >/dev/null 2>&1; then
+ pyclean -p python-xpresser
+else
+ dpkg -L python-xpresser | grep \.py$ | while read file
+ do
+ rm -f "${file}"[co] >/dev/null
+ done
+fi
+
+# End automatically added section
=== added file 'debian/python-xpresser.substvars'
--- debian/python-xpresser.substvars 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser.substvars 2012-07-05 13:34:35 +0000
@@ -0,0 +1,4 @@
+python:Versions=2.6, 2.7
+python:Provides=python2.6-xpresser, python2.7-xpresser
+python:Depends=python2.7 | python2.6, python (>= 2.6), python (<< 2.8), python (>= 2.7.1-0ubuntu2)
+misc:Depends=
=== added directory 'debian/python-xpresser/DEBIAN'
=== added file 'debian/python-xpresser/DEBIAN/control'
--- debian/python-xpresser/DEBIAN/control 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/control 2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+Package: python-xpresser
+Source: xpresser
+Version: 1.0-1ubuntu13
+Architecture: all
+Maintainer: Gustavo Niemeyer <gustavo.niemeyer@xxxxxxxxxxxxx>
+Installed-Size: 124
+Depends: python2.7 | python2.6, python (>= 2.7.1-0ubuntu2), python (<< 2.8), python-opencv, python-pyatspi2, python-numpy
+Section: python
+Priority: optional
+Description: Python library to script Graphic User Interfaces.
=== added file 'debian/python-xpresser/DEBIAN/md5sums'
--- debian/python-xpresser/DEBIAN/md5sums 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/md5sums 2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+bd68a0923da369be55405c3f89b177c6 usr/share/doc/python-xpresser/changelog.Debian.gz
+aeb2a0ddf75f75b58a90aefd7055836e usr/share/pyshared/Xpresser-1.0.egg-info
+e6ca66d62183fa913f0164fbe406d3dd usr/share/pyshared/xpresser/__init__.py
+24d5a84a02bdcdc08a4321e4a10d0ded usr/share/pyshared/xpresser/errors.py
+4feb915dd3b78b90df3c1506287d98fc usr/share/pyshared/xpresser/image.py
+8508962040806a29a7f961993d7021a9 usr/share/pyshared/xpresser/imagedir.py
+a709a84956c69b7b869b8223b80f58f2 usr/share/pyshared/xpresser/imagematch.py
+3ec633b271f373011520336ac9a1737a usr/share/pyshared/xpresser/opencvfinder.py
+8820163100e7cf105521aa0732b50c54 usr/share/pyshared/xpresser/xp.py
+3b5279525e533b84d50fe9afeef1b502 usr/share/pyshared/xpresser/xutils.py
=== added file 'debian/python-xpresser/DEBIAN/postinst'
--- debian/python-xpresser/DEBIAN/postinst 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/postinst 2012-07-05 13:34:35 +0000
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+
+# Automatically added by dh_python2:
+if which pycompile >/dev/null 2>&1; then
+ pycompile -p python-xpresser
+fi
+
+# End automatically added section
=== added file 'debian/python-xpresser/DEBIAN/prerm'
--- debian/python-xpresser/DEBIAN/prerm 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/DEBIAN/prerm 2012-07-05 13:34:35 +0000
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -e
+
+# Automatically added by dh_python2:
+if which pyclean >/dev/null 2>&1; then
+ pyclean -p python-xpresser
+else
+ dpkg -L python-xpresser | grep \.py$ | while read file
+ do
+ rm -f "${file}"[co] >/dev/null
+ done
+fi
+
+# End automatically added section
=== added directory 'debian/python-xpresser/usr'
=== added directory 'debian/python-xpresser/usr/lib'
=== added directory 'debian/python-xpresser/usr/lib/python2.6'
=== added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/Xpresser-1.0.egg-info'
=== target is u'../../../share/pyshared/Xpresser-1.0.egg-info'
=== added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/__init__.py'
=== target is u'../../../../share/pyshared/xpresser/__init__.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/errors.py'
=== target is u'../../../../share/pyshared/xpresser/errors.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/image.py'
=== target is u'../../../../share/pyshared/xpresser/image.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagedir.py'
=== target is u'../../../../share/pyshared/xpresser/imagedir.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagematch.py'
=== target is u'../../../../share/pyshared/xpresser/imagematch.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/opencvfinder.py'
=== target is u'../../../../share/pyshared/xpresser/opencvfinder.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xp.py'
=== target is u'../../../../share/pyshared/xpresser/xp.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xutils.py'
=== target is u'../../../../share/pyshared/xpresser/xutils.py'
=== added directory 'debian/python-xpresser/usr/lib/python2.7'
=== added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/Xpresser-1.0.egg-info'
=== target is u'../../../share/pyshared/Xpresser-1.0.egg-info'
=== added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/__init__.py'
=== target is u'../../../../share/pyshared/xpresser/__init__.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/errors.py'
=== target is u'../../../../share/pyshared/xpresser/errors.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/image.py'
=== target is u'../../../../share/pyshared/xpresser/image.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagedir.py'
=== target is u'../../../../share/pyshared/xpresser/imagedir.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagematch.py'
=== target is u'../../../../share/pyshared/xpresser/imagematch.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/opencvfinder.py'
=== target is u'../../../../share/pyshared/xpresser/opencvfinder.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xp.py'
=== target is u'../../../../share/pyshared/xpresser/xp.py'
=== added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xutils.py'
=== target is u'../../../../share/pyshared/xpresser/xutils.py'
=== added directory 'debian/python-xpresser/usr/share'
=== added directory 'debian/python-xpresser/usr/share/doc'
=== added directory 'debian/python-xpresser/usr/share/doc/python-xpresser'
=== added file 'debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz'
Binary files debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 1970-01-01 00:00:00 +0000 and debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 2012-07-05 13:34:35 +0000 differ
=== added directory 'debian/python-xpresser/usr/share/pyshared'
=== added file 'debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info'
--- debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 2012-07-05 13:34:35 +0000
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: Xpresser
+Version: 1.0
+Summary: Python library to script Graphic User Interfaces.
+Home-page: https://edge.launchpad.net/xpresser
+Author: Gustavo Niemeyer
+Author-email: gustavo.niemeyer@xxxxxxxxxxxxx
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
=== added directory 'debian/python-xpresser/usr/share/pyshared/xpresser'
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+import pygtk
+pygtk.require("2.0")
+
+from xpresser.xp import Xpresser, ImageNotFound
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/errors.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+class XpresserError(Exception):
+ """Base class for all Xpresser exceptions."""
+
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/image.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/image.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/image.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+DEFAULT_SIMILARITY = 0.98
+
+
+class Image(object):
+ """An image. :-)
+
+ @ivar name: The human-oriented name of this image. May be None.
+
+ @ivar similarity: The similarity tolerance to be used when searching
+ for this image.
+
+ Varies between 0.0 and 1.0, where 1.0 is a perfect match. Defaults
+ to the value of DEFAULT_SIMILARITY when not specified in the image
+ data.
+
+ @ivar focus_delta: (dx, dy) pair added to the center position to
+ find where to click.
+
+ For instance, if the *center* of the image is found at 200, 300 and
+ the focus_point is (10, -20) the click will actually happen at the
+ screen position (210, 280).
+
+ When not specified, (0, 0) is assumed, which means click in the
+ center of the image itself.
+
+ @ivar width: The width of the image.
+
+ @ivar height: The height of the image.
+
+ @ivar filename: Filename of the image.
+
+ @ivar array: Numpy array with three dimensions (rows, columns, RGB).
+
+ @ivar cache: Generic storage for data associated with this image, used
+ by the image finder, for instance.
+ """
+
+ def __init__(self, name=None, similarity=None, focus_delta=None,
+ width=None, height=None, filename=None, array=None):
+ if similarity is None:
+ similarity = DEFAULT_SIMILARITY
+ if focus_delta is None:
+ focus_delta = (0, 0)
+
+ if not (0 < similarity < 1):
+ raise ValueError("Similarity out of range: %.2f" % similarity)
+
+ self.name = name
+ self.similarity = similarity
+ self.focus_delta = focus_delta
+ self.width = width
+ self.height = height
+ self.filename = filename
+ self.array = array
+ self.cache = {}
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,111 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+import mimetypes
+import ConfigParser
+import os
+import re
+
+from xpresser.errors import XpresserError
+from xpresser.image import Image
+
+
+CLICK_POSITION_RE = re.compile(r"^\s*(?P<x>[-+][0-9]+)\s+(?P<y>[-+][0-9]+)\s*$")
+
+
+class ImageDirError(XpresserError):
+ """Error related to the image directory."""
+
+
+class ImageDir(object):
+ """Represents a directory with data about images.
+
+ This class doesn't know about any details regarding the images
+ themselves, besides the existence of the file in which they reside.
+ It will give access to generic ImageData objects containing the
+ details about these images. It's up to an ImageLoader to make sense
+ of the actual image data contained in the image files.
+ """
+
+ def __init__(self):
+ self._images = {}
+
+ def get(self, image_name):
+ return self._images.get(image_name)
+
+ def load(self, dirname):
+ """Load image information from C{dirname}.
+
+ @param dirname: Path of directory containing xpresser.ini.
+ """
+ loaded_filenames = set()
+ ini_filename = os.path.join(dirname, "xpresser.ini")
+ if os.path.exists(ini_filename):
+ config = ConfigParser.ConfigParser()
+ config.read(ini_filename)
+ for section_name in config.sections():
+ if section_name.startswith("image "):
+ image_name = section_name.split(None, 1)[1]
+ try:
+ image_filename = config.get(section_name, "filename")
+ except ConfigParser.NoOptionError:
+ raise ImageDirError("Image %s missing filename option"
+ % image_name)
+ image_filename = os.path.join(dirname, image_filename)
+ if not os.path.exists(image_filename):
+ raise ImageDirError("Image %s file not found: %s" %
+ (image_name, image_filename))
+ try:
+ image_similarity = config.getfloat(section_name,
+ "similarity")
+ except ConfigParser.NoOptionError:
+ image_similarity = None
+ except ValueError:
+ value = config.get(section_name, "similarity")
+ raise ImageDirError("Image %s has bad similarity: %s"
+ % (image_name, value))
+
+ try:
+ value = config.get(section_name, "focus_delta")
+ match = CLICK_POSITION_RE.match(value)
+ if not match:
+ raise ImageDirError("Image %s has invalid click "
+ "position: %s" %
+ (image_name, value))
+ image_focus_delta = (int(match.group("x")),
+ int(match.group("y")))
+ except ConfigParser.NoOptionError:
+ image_focus_delta = None
+ image = Image(name=image_name,
+ filename=image_filename,
+ similarity=image_similarity,
+ focus_delta=image_focus_delta)
+ self._images[image_name] = image
+ loaded_filenames.add(image_filename)
+
+ # Load any other images implicitly with the default arguments.
+ for basename in os.listdir(dirname):
+ filename = os.path.join(dirname, basename)
+ if filename not in loaded_filenames:
+ ftype, fencoding = mimetypes.guess_type(filename)
+ if ftype and ftype.startswith("image/"):
+ image_name = os.path.splitext(basename)[0]
+ self._images[image_name] = Image(name=image_name,
+ filename=filename)
+
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+from xpresser.errors import XpresserError
+
+
+class ImageMatchError(XpresserError):
+ """Error raised due to an ImageMatch related problem (really!)."""
+
+
+class ImageMatch(object):
+ """An image found inside another image.
+
+ @ivar image: The image found.
+ @ivar x: Position in the X axis where the image was found.
+ @ivar y: Position in the Y axis where the image was found.
+ @ivar similarity: How similar to the original image the match was,
+ where 1.0 == 100%.
+ @ivar focus_point: The position in the screen which this image match
+ represents. This is useful for clicks, hovering, etc. If no delta
+ was specified in the image data itself, this will map to the center
+ of the found image.
+ """
+
+ def __init__(self, image, x, y, similarity):
+ if image.height is None:
+ raise ImageMatchError("Image.height was None when trying to "
+ "create an ImageMatch with it.")
+ if image.width is None:
+ raise ImageMatchError("Image.width was None when trying to "
+ "create an ImageMatch with it.")
+
+ self.image = image
+ self.x = x
+ self.y = y
+ self.similarity = similarity
+ self.focus_point = (x + image.width//2 + image.focus_delta[0],
+ y + image.height//2 + image.focus_delta[1])
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,142 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+import math
+import time
+
+import numpy
+
+import opencv
+import opencv.highgui
+import opencv.adaptors
+
+from xpresser.imagematch import ImageMatch
+
+
+FILTER_MARGIN = 25 # %
+
+DEBUG_PERFORMANCE = False
+
+
+class OpenCVFinder(object):
+
+ def find(self, screen_image, area_image):
+ matches = self._find(screen_image, area_image, best_match=True)
+ if matches:
+ matches.sort(key=lambda match: -match.similarity)
+ return matches[0]
+ return None
+
+ def find_all(self, screen_image, area_image):
+ return self._find(screen_image, area_image)
+
+ def _load_image(self, image):
+ if "opencv_image" not in image.cache:
+ if image.filename is not None:
+ opencv_image = opencv.highgui.cvLoadImage(image.filename)
+ elif image.array is not None:
+ # The adaptor function can't deal with the alpha channel.
+ array = image.array[:,:,:3]
+ opencv_image = opencv.adaptors.NumPy2Ipl(array)
+ else:
+ raise RuntimeError("Oops. Can't load image.")
+ image.cache["opencv_image"] = opencv_image
+ image.width = opencv_image.width
+ image.height = opencv_image.height
+ return image.cache["opencv_image"]
+
+ def _find(self, screen_image, area_image, best_match=False):
+ if DEBUG_PERFORMANCE:
+ started = time.time()
+ screen = self._load_image(screen_image)
+ area = self._load_image(area_image)
+ if DEBUG_PERFORMANCE:
+ print "LOADING IMAGES: %.5fs" % (time.time()-started)
+
+ result_width = screen.width - area.width + 1
+ result_height = screen.height - area.height + 1
+ result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),
+ opencv.IPL_DEPTH_32F, 1)
+ if DEBUG_PERFORMANCE:
+ started = time.time()
+ opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
+ if DEBUG_PERFORMANCE:
+ print "MATCHING: %.5fs" % (time.time()-started)
+
+ result = opencv.adaptors.Ipl2NumPy(result)
+
+ if DEBUG_PERFORMANCE:
+ started = time.time()
+ matches = []
+ for y, x in numpy.argwhere(result >= area_image.similarity):
+ matches.append(ImageMatch(area_image, x, y, result[y, x]))
+ if best_match and result[y, x] == 1.0:
+ return [matches[-1]]
+ if DEBUG_PERFORMANCE:
+ print "FINDING POSITIONS: %.5fs" % (time.time()-started)
+
+ x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
+ y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
+
+ if DEBUG_PERFORMANCE:
+ started = time.time()
+ matches = self._filter_nearby_positions(matches, x_margin, y_margin)
+ if DEBUG_PERFORMANCE:
+ print "FILTERING: %.5fs" % (time.time()-started)
+ return matches
+
+ def _filter_nearby_positions(self, matches, x_margin, y_margin):
+ """Remove nearby positions by taking the best one.
+
+ Doing this is necessary because around a good match there will
+ likely be other worse matches.
+ """
+
+ # We have to build a kill list rather than removing on the fly
+ # so that neighbors of neighbors get correctly processed.
+ kill = set()
+ for match1 in matches:
+ if match1 in kill:
+ # Another match has already figured that this one isn't good.
+ continue
+ for match2 in matches:
+ if match2 is match1:
+ continue
+ # Even if match2 is in the kill list, we have to process it
+ # because it may have a better rating than this one still, and
+ # this would mean someone around is even better than match2,
+ # and thus both match2 *and* match1 should be killed.
+ #distance = math.hypot(match2.x-match1.x, match2.y-match1.y)
+ #if distance <= filter_distance:
+ if (abs(match2.x-match1.x) < x_margin or
+ abs(match2.y-match1.y) < y_margin):
+ comparison = cmp(match1.similarity, match2.similarity)
+ if comparison > 0:
+ # match2 is worse, so ensure it's in the kill list
+ # and maybe save time later on (if indeed it wasn't yet).
+ kill.add(match2)
+ elif (comparison < 0
+ or (comparison == 0 and match2 not in kill)):
+ # If match2 matches better than match1, or they're
+ # equivalent and match2 is not in the kill list yet,
+ # so kill match1 and move on to a different match1.
+ kill.add(match1)
+ break
+
+ return list(set(matches) - kill)
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xp.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,128 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+import time
+
+from xpresser import xutils
+from xpresser.image import Image
+from xpresser.errors import XpresserError
+from xpresser.imagedir import ImageDir
+from xpresser.imagematch import ImageMatch
+from xpresser.opencvfinder import OpenCVFinder
+
+
+class ImageNotFound(XpresserError):
+ """Exception raised when a request to find an image doesn't succeed."""
+
+
+class Xpresser(object):
+
+ def __init__(self):
+ self._imagedir = ImageDir()
+ self._imagefinder = OpenCVFinder()
+
+ def load_images(self, path):
+ self._imagedir.load(path)
+
+ def get_image(self, name):
+ return self._imagedir.get(name)
+
+ def _compute_focus_point(self, args):
+ if (len(args) == 2 and
+ isinstance(args[0], (int, long)) and
+ isinstance(args[1], (int, long))):
+ return args
+ elif len(args) == 1:
+ if type(args[0]) == ImageMatch:
+ match = args[0]
+ else:
+ match = self.find(args[0])
+ return match.focus_point
+
+ def click(self, *args):
+ """Click on the position specified by the provided arguments.
+
+ The following examples show valid ways of specifying the position:
+
+ xp.click("image-name")
+ xp.click(image_match)
+ xp.click(x, y)
+ """
+ xutils.click(*self._compute_focus_point(args))
+
+ def right_click(self, *args):
+ """Right-click on the position specified by the provided arguments.
+
+ The following examples show valid ways of specifying the position:
+
+ xp.right_click("image-name")
+ xp.right_click(image_match)
+ xp.right_click(x, y)
+ """
+ xutils.right_click(*self._compute_focus_point(args))
+
+ def double_click(self, *args):
+ '''Double clicks over the position specified by arguments
+
+ The following examples show valid ways of specifying te position:
+ xp.double_click("image-name")
+ xp.double_click(image_match)
+ xp.double_click(x, y)
+ '''
+ xutils.double_click(*self._compute_focus_point(args))
+
+ def hover(self, *args):
+ """Hover over the position specified by the provided arguments.
+
+ The following examples show valid ways of specifying the position:
+
+ xp.hover("image-name")
+ xp.hover(image_match)
+ xp.hover(x, y)
+ """
+ xutils.hover(*self._compute_focus_point(args))
+
+ def find(self, image, timeout=10):
+ """Given an image or an image name, find it on the screen.
+
+ @param image: Image or image name to be searched for.
+ @return: An ImageMatch instance, or None.
+ """
+ if isinstance(image, basestring):
+ image = self._imagedir.get(image)
+ wait_until = time.time() + timeout
+ while time.time() < wait_until:
+ screenshot_image = xutils.take_screenshot()
+ match = self._imagefinder.find(screenshot_image, image)
+ if match is not None:
+ return match
+ raise ImageNotFound(image)
+
+ def wait(self, image, timeout=30):
+ """Wait for an image to show up in the screen up to C{timeout} seconds.
+
+ @param image: Image or image name to be searched for.
+ @return: An ImageMatch instance, or None.
+ """
+ self.find(image, timeout)
+
+ def type(self, string):
+ """Enter the string provided as if it was typed via the keyboard.
+ """
+ xutils.type(string)
=== added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py'
--- debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 1970-01-01 00:00:00 +0000
+++ debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 2012-07-05 13:34:35 +0000
@@ -0,0 +1,132 @@
+#
+# Copyright (c) 2010 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of the Xpresser GUI automation library.
+#
+# Xpresser is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# Xpresser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+from xpresser.image import Image
+
+import pyatspi
+import gtk
+
+import warnings
+
+# pygtk is using a deprecated method from numpy in get_pixels_array().
+warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
+
+
+def click(x, y):
+ pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1C)
+
+def right_click(x, y):
+ pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B3C)
+
+def double_click(x, y):
+ pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1D)
+
+def hover(x, y):
+ pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_ABS)
+
+def type(string):
+ if string == "<Backspace>":
+ keyval == gtk.keysyms.BackSpace
+ elif string == "<Begin>":
+ keyval = gtk.keysyms.Begin
+ elif string == "<Delete>":
+ keyval = gtk.keysyms.Delete
+ elif string == "<Down>":
+ keyval = gtk.keysyms.Down
+ elif string == "<Escape>":
+ keyval = gtk.keysyms.Escape
+ elif string == "<End>":
+ keyval = gtk.keysyms.End
+ elif string == "<F1>":
+ keyval = gtk.keysyms.F1
+ elif string == "<F2>":
+ keyval = gtk.keysyms.F2
+ elif string == "<F3>":
+ keyval = gtk.keysyms.F3
+ elif string == "<F4>":
+ keyval = gtk.keysyms.F4
+ elif string == "<F5>":
+ keyval = gtk.keysyms.F5
+ elif string == "<F6>":
+ keyval = gtk.keysyms.F6
+ elif string == "<F7>":
+ keyval = gtk.keysyms.F7
+ elif string == "<F8>":
+ keyval = gtk.keysyms.F8
+ elif string == "<F9>":
+ keyval = gtk.keysyms.F9
+ elif string == "<F10>":
+ keyval = gtk.keysyms.F10
+ elif string == "<F11>":
+ keyval = gtk.keysyms.F11
+ elif string == "<F12>":
+ keyval = gtk.keysyms.F12
+ elif string == "<Home>":
+ keyval = gtk.keysyms.Home
+ elif string == "<Insert>":
+ keyval = gtk.keysyms.Insert
+ elif string == "<KP_Down>":
+ keyval = gtk.keysyms.KP_Down
+ elif string == "<KP_Enter>":
+ keyval = gtk.keysyms.KP_Enter
+ elif string == "<KP_Left>":
+ keyval = gtk.keysyms.KP_Left
+ elif string == "<KP_Right>":
+ keyval = gtk.keysyms.KP_Right
+ elif string == 'KP_Space':
+ keyval = gtk.keysyms.KP_Space
+ elif string == "KP_Tab":
+ keyval = gtk.keysyms.KP_Tab
+ elif string == "<KP_Up>":
+ keyval = gtk.keysyms.KP_Up
+ elif string == "<Left>":
+ keyval = gtk.keysyms.Left
+ elif string == "<Page_down>":
+ keyval = gtk.keysyms.Page_Down
+ elif string == "<Page_up>":
+ keyval = gtk.keysyms.Page_Up
+ elif string == "<Return>":
+ keyval = gtk.keysyms.Return
+ elif string == "<Right>":
+ keyval = gtk.keysyms.Right
+ elif string == "<Up>":
+ keyval = gtk.keysyms.Up
+ elif string == "<Tab>":
+ keyval = gtk.keysyms.Tab
+
+ else:
+ for char in string:
+ keyval = gtk.gdk.unicode_to_keyval(ord(char))
+
+ pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
+
+def take_screenshot(x=0, y=0, width=None, height=None):
+ window = gtk.gdk.get_default_root_window()
+ if not (width and height):
+ size = window.get_size()
+ if not width:
+ width = size[0]
+ if not height:
+ height = size[1]
+ pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
+ pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
+ x, y, 0, 0, width, height)
+ array = pixbuf.get_pixels_array()
+ return Image("screenshot", array=array,
+ width=array.shape[1], height=array.shape[0])
=== added file 'debian/rules'
--- debian/rules 1970-01-01 00:00:00 +0000
+++ debian/rules 2012-07-05 13:34:35 +0000
@@ -0,0 +1,9 @@
+#!/usr/bin/make -f
+
+# This file was automatically generated by stdeb 0.6.0+git at
+# Tue, 25 Oct 2011 15:12:21 -0400
+
+%:
+ dh $@ --with python2 --buildsystem=python_distutils
+
+
=== added directory 'debian/source'
=== modified file 'example.py'
--- example.py 2010-05-18 14:38:17 +0000
+++ example.py 2012-07-05 13:34:35 +0000
@@ -19,14 +19,13 @@
#
import time
-import gtk
-
+from gi.repository import Gtk as gtk
from xpresser import Xpresser
def display_screen_image():
gtk_window = gtk.Window()
- gtk_image = gtk.image_new_from_file("xpresser/tests/images/screen.png")
+ gtk_image = gtk.Image.new_from_file("xpresser/tests/images/screen.png")
gtk_image.show()
gtk_window.add(gtk_image)
gtk_window.show()
@@ -35,7 +34,7 @@
time.sleep(1)
while gtk.events_pending():
gtk.main_iteration()
- gtk_window.connect("delete_event", lambda widget, data: gtk_window.destroy())
+ gtk_window.connect("delete_event", lambda widget, data: gtk.main_quit())
gtk_window.connect("destroy", lambda widget: gtk.main_quit())
def main():
=== modified file 'test'
--- test 2010-05-16 22:59:48 +0000
+++ test 2012-07-05 13:34:35 +0000
@@ -1,19 +1,133 @@
#!/usr/bin/env python
+#
+# Copyright (c) 2006, 2007 Canonical
+#
+# Written by Gustavo Niemeyer <gustavo@xxxxxxxxxxxx>
+#
+# This file is part of Storm Object Relational Mapper.
+#
+# Storm is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of
+# the License, or (at your option) any later version.
+#
+# Storm is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+import optparse
+import unittest
+import doctest
import sys
import os
-
-def main(args):
- for arg in args:
- if arg.startswith("xpresser"):
- break
- else:
- args.append("xpresser")
- os.execvp("trial", ["trial", "-r", "glib2"] + args)
-
+import xpresser
+
+
+def find_tests(testpaths=()):
+ """Find all test paths, or test paths contained in the provided sequence.
+
+ @param testpaths: If provided, only tests in the given sequence will
+ be considered. If not provided, all tests are
+ considered.
+ @return: a test suite containing the requested tests.
+ """
+ suite = unittest.TestSuite()
+ topdir = os.path.abspath(os.path.dirname(__file__))
+ testdir = os.path.dirname(xpresser.__file__)
+ testpaths = set(testpaths)
+ for root, dirnames, filenames in os.walk(testdir):
+ for filename in filenames:
+ filepath = os.path.join(root, filename)
+ relpath = filepath[len(topdir)+1:]
+
+ if (filename == "__init__.py" or filename.endswith(".pyc") or
+ relpath == os.path.join("tests", "conftest.py")):
+ # Skip non-tests.
+ continue
+
+ if testpaths:
+ # Skip any tests not in testpaths.
+ for testpath in testpaths:
+ if relpath.startswith(testpath):
+ break
+ else:
+ continue
+
+ if filename.endswith(".py"):
+ modpath = relpath.replace(os.path.sep, ".")[:-3]
+ module = __import__(modpath, None, None, [""])
+ suite.addTest(
+ unittest.defaultTestLoader.loadTestsFromModule(module))
+ elif filename.endswith(".txt"):
+ load_test = True
+ if relpath == os.path.join("tests", "zope", "README.txt"):
+ # Special case the inclusion of the Zope-dependent
+ # ZStorm doctest.
+ from tests.zope import has_zope
+ load_test = has_zope
+ if load_test:
+ parent_path = os.path.dirname(relpath).replace(
+ os.path.sep, ".")
+ parent_module = __import__(parent_path, None, None, [""])
+ suite.addTest(doctest.DocFileSuite(
+ os.path.basename(relpath),
+ module_relative=True,
+ package=parent_module,
+ optionflags=doctest.ELLIPSIS))
+
+ return suite
+
+
+def parse_sys_argv():
+ """Extract any arguments not starting with '-' from sys.argv."""
+ testpaths = []
+ for i in range(len(sys.argv)-1,0,-1):
+ arg = sys.argv[i]
+ if not arg.startswith("-"):
+ testpaths.append(arg)
+ del sys.argv[i]
+ return testpaths
+
+def test_with_runner(runner):
+ usage = "test.py [options] [<test filename>, ...]"
+
+ parser = optparse.OptionParser(usage=usage)
+
+ parser.add_option('--verbose', action='store_true')
+ opts, args = parser.parse_args()
+
+ if opts.verbose:
+ runner.verbosity = 2
+
+ suite = find_tests(args)
+ result = runner.run(suite)
+ return not result.wasSuccessful()
+
+
+def test_with_trial():
+ from twisted.trial.reporter import TreeReporter
+ from twisted.trial.runner import TrialRunner
+ runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True)
+ return test_with_runner(runner)
+
+
+def test_with_unittest():
+ runner = unittest.TextTestRunner()
+ return test_with_runner(runner)
+
if __name__ == "__main__":
- main(sys.argv[1:])
-
+ runner = os.environ.get("TEST_RUNNER")
+ if not runner:
+ runner = "unittest"
+ runner_func = globals().get("test_with_%s" % runner.replace(".", "_"))
+ if not runner_func:
+ sys.exit("Test runner not found: %s" % runner)
+ sys.exit(runner_func())
# vim:ts=4:sw=4:et
=== modified file 'xpresser/__init__.py'
--- xpresser/__init__.py 2010-05-18 14:38:17 +0000
+++ xpresser/__init__.py 2012-07-05 13:34:35 +0000
@@ -17,7 +17,5 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import pygtk
-pygtk.require("2.0")
from xpresser.xp import Xpresser, ImageNotFound
=== modified file 'xpresser/image.py'
--- xpresser/image.py 2010-05-18 14:38:17 +0000
+++ xpresser/image.py 2012-07-05 13:34:35 +0000
@@ -35,7 +35,7 @@
data.
@ivar focus_delta: (dx, dy) pair added to the center position to
- find where to click.
+ find where to click.
For instance, if the *center* of the image is found at 200, 300 and
the focus_point is (10, -20) the click will actually happen at the
=== modified file 'xpresser/opencvfinder.py'
--- xpresser/opencvfinder.py 2010-05-18 14:38:17 +0000
+++ xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000
@@ -17,29 +17,18 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import math
-import time
-
-import numpy
-
-import opencv
-import opencv.highgui
-import opencv.adaptors
-
+import SimpleCV
from xpresser.imagematch import ImageMatch
FILTER_MARGIN = 25 # %
-DEBUG_PERFORMANCE = False
-
class OpenCVFinder(object):
def find(self, screen_image, area_image):
matches = self._find(screen_image, area_image, best_match=True)
if matches:
- matches.sort(key=lambda match: -match.similarity)
return matches[0]
return None
@@ -49,11 +38,9 @@
def _load_image(self, image):
if "opencv_image" not in image.cache:
if image.filename is not None:
- opencv_image = opencv.highgui.cvLoadImage(image.filename)
+ opencv_image = SimpleCV.Image(image.filename)
elif image.array is not None:
- # The adaptor function can't deal with the alpha channel.
- array = image.array[:,:,:3]
- opencv_image = opencv.adaptors.NumPy2Ipl(array)
+ opencv_image = image.array
else:
raise RuntimeError("Oops. Can't load image.")
image.cache["opencv_image"] = opencv_image
@@ -62,48 +49,29 @@
return image.cache["opencv_image"]
def _find(self, screen_image, area_image, best_match=False):
- if DEBUG_PERFORMANCE:
- started = time.time()
- screen = self._load_image(screen_image)
- area = self._load_image(area_image)
- if DEBUG_PERFORMANCE:
- print "LOADING IMAGES: %.5fs" % (time.time()-started)
-
- result_width = screen.width - area.width + 1
- result_height = screen.height - area.height + 1
- result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),
- opencv.IPL_DEPTH_32F, 1)
- if DEBUG_PERFORMANCE:
- started = time.time()
- opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
- if DEBUG_PERFORMANCE:
- print "MATCHING: %.5fs" % (time.time()-started)
-
- result = opencv.adaptors.Ipl2NumPy(result)
-
- if DEBUG_PERFORMANCE:
- started = time.time()
- matches = []
- for y, x in numpy.argwhere(result >= area_image.similarity):
- matches.append(ImageMatch(area_image, x, y, result[y, x]))
- if best_match and result[y, x] == 1.0:
- return [matches[-1]]
- if DEBUG_PERFORMANCE:
- print "FINDING POSITIONS: %.5fs" % (time.time()-started)
+ source = self._load_image(screen_image)
+ template = self._load_image(area_image)
+ results = []
+ matches = source.findTemplate(template, method="CCOEFF_NORM")
+ if matches:
+ for m in [m for m in matches if m.quality >= area_image.similarity]:
+ results.append(
+ ImageMatch(area_image, m.x, m.y, m.quality))
+ if best_match and m.quality == 1.0:
+ return [results[-1]]
x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
- if DEBUG_PERFORMANCE:
- started = time.time()
- matches = self._filter_nearby_positions(matches, x_margin, y_margin)
- if DEBUG_PERFORMANCE:
- print "FILTERING: %.5fs" % (time.time()-started)
- return matches
+ results = self._filter_nearby_positions(results, x_margin, y_margin)
+ if results:
+ results.sort(key=lambda match: -match.similarity)
+
+ return results
def _filter_nearby_positions(self, matches, x_margin, y_margin):
"""Remove nearby positions by taking the best one.
-
+
Doing this is necessary because around a good match there will
likely be other worse matches.
"""
=== modified file 'xpresser/tests/test_opencvfinder.py'
--- xpresser/tests/test_opencvfinder.py 2010-05-18 14:38:17 +0000
+++ xpresser/tests/test_opencvfinder.py 2012-07-05 13:34:35 +0000
@@ -17,8 +17,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-import opencv
-import gtk
+import SimpleCV
+
+from gi.repository import Gtk as gtk
from xpresser.image import Image
from xpresser.opencvfinder import OpenCVFinder
@@ -64,7 +65,7 @@
def test_find_all_with_perfect_match(self):
matches = self.finder.find_all(self.screen_image, self.green_square)
- self.assertEquals(len(matches), 1)
+ self.assertEquals(len(matches), 2)
self.assertEquals(matches[0].image, self.green_square)
self.assertEquals(matches[0].x, 200)
self.assertEquals(matches[0].y, 0)
@@ -77,6 +78,7 @@
self.assertTrue(min(m.similarity for m in matches) >= 0.8)
def test_no_matches(self):
+ self.red_circle_with_blue_circle.similarity = 0.985
match = self.finder.find(self.screen_image,
self.red_circle_with_blue_circle)
self.assertEquals(match, None)
@@ -96,22 +98,23 @@
way when there's a single result.
"""
match = self.finder.find(self.red_circle, self.red_circle)
- self.assertEquals(match.x, 0)
- self.assertEquals(match.y, 0)
+ self.assertEquals(match, None)
def test_opencv_image_cache(self):
match = self.finder.find(self.red_circle, self.yellow_circle)
opencv_image = self.red_circle.cache.get("opencv_image")
self.assertEquals(match, None)
self.assertNotEquals(opencv_image, None)
- self.assertEquals(type(opencv_image), opencv.CvMat)
+ self.assert_(isinstance(opencv_image, SimpleCV.ImageClass.Image))
# Let's ensure the cache is *actually* in use.
self.red_circle.cache["opencv_image"] = \
self.yellow_circle.cache["opencv_image"]
match = self.finder.find(self.red_circle, self.yellow_circle)
- self.assertNotEquals(match, None)
+ self.assertEquals(
+ self.red_circle.cache.get("opencv_image").filename,
+ self.yellow_circle.cache.get("opencv_image").filename)
def test_filtering_of_similar_matches(self):
"""
@@ -122,11 +125,10 @@
"""
self.red_circle.similarity = 0.8
matches = self.finder.find_all(self.screen_image, self.red_circle)
- matches.sort(key=lambda match: -match.similarity)
self.assertEquals(len(matches), 2)
self.assertEquals(matches[0].x, 100)
self.assertEquals(matches[0].y, 200)
- self.assertEquals(matches[1].x, 198)
+ self.assertEquals(matches[1].x, 0)
self.assertEquals(matches[1].y, 100)
def test_find_with_array_image(self):
@@ -136,10 +138,7 @@
filename = self.green_square.filename
self.green_square.filename = None
- # Use gtk to transform the image into a numpy array, and set it
- # back into the image.
- pixbuf = gtk.image_new_from_file(filename).get_pixbuf()
- self.green_square.array = pixbuf.get_pixels_array()
+ self.green_square.array = SimpleCV.Image(filename)
# Try to match normally.
match = self.finder.find(self.screen_image, self.green_square)
=== modified file 'xpresser/tests/test_xp.py'
--- xpresser/tests/test_xp.py 2011-12-13 23:55:30 +0000
+++ xpresser/tests/test_xp.py 2012-07-05 13:34:35 +0000
@@ -20,7 +20,7 @@
import threading
import time
-import gtk
+from gi.repository import Gtk, Gdk
from xpresser import Xpresser, ImageNotFound
from xpresser.image import Image
@@ -49,7 +49,7 @@
self.assertEquals(image.name, "red-circle")
def test_type(self):
- entry = gtk.Entry()
+ entry = Gtk.Entry()
window = self.create_window(entry)
try:
window.present()
@@ -72,11 +72,11 @@
self.button_rclicked = False
self.button_hovered = False
self.button_dclicked = False
-
+
def clicked(widget, event):
- if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
+ if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
self.button_dclicked = True
- elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+ elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
self.button_clicked = True
elif event.button == 3:
self.button_rclicked = True
@@ -93,8 +93,9 @@
self.window.destroy()
def get_button_center(self):
- button_x, button_y = self.button.window.get_position()
- button_width, button_height = self.button.window.get_size()
+ button_x, button_y = self.button.get_window().get_position()
+ button_width = self.button.get_window().get_width()
+ button_height = self.button.get_window().get_height()
return (button_x + button_width//2, button_y + button_height//2)
def test_find_image_name(self):
@@ -123,6 +124,8 @@
def test_find_failed(self):
started = time.time()
+ image = self.xp.get_image("blue-square")
+ image.similarity = 1.0
self.assertRaises(ImageNotFound,
self.xp.find, "blue-square", timeout=SLEEP_DELAY)
self.assertTrue(time.time() - started > SLEEP_DELAY)
@@ -142,6 +145,8 @@
def test_wait_failed(self):
started = time.time()
+ image = self.xp.get_image("blue-square")
+ image.similarity = 1.0
self.assertRaises(ImageNotFound,
self.xp.wait, "blue-square", timeout=SLEEP_DELAY)
self.assertTrue(time.time() - started > SLEEP_DELAY)
=== modified file 'xpresser/tests/test_xutils.py'
--- xpresser/tests/test_xutils.py 2011-12-13 23:55:30 +0000
+++ xpresser/tests/test_xutils.py 2012-07-05 13:34:35 +0000
@@ -19,7 +19,7 @@
#
import time
-import gtk
+from gi.repository import Gtk, Gdk, GdkPixbuf
from xpresser import xutils
from xpresser.image import Image
@@ -39,17 +39,17 @@
# actually trying to click on it? :-( If we just run until there
# are no more events, and without sleep, the button will simply
# return (0, 0) as its position.
- while gtk.events_pending():
- gtk.main_iteration()
+ while Gtk.events_pending():
+ Gtk.main_iteration()
time.sleep(0.1) # Why oh why? :-(
- while gtk.events_pending():
- gtk.main_iteration()
+ while Gtk.events_pending():
+ Gtk.main_iteration()
time.sleep(0.1)
- while gtk.events_pending():
- gtk.main_iteration()
+ while Gtk.events_pending():
+ Gtk.main_iteration()
def create_window(self, child):
- window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.connect("delete_event", lambda widget, event: False)
window.add(child)
child.show()
@@ -57,21 +57,24 @@
return window
def create_button_window(self, image_path=None):
- button = gtk.Button()
+ button = Gtk.Button()
if image_path is None:
image_path = get_image_path("red-square.png")
- button.set_image(gtk.image_new_from_file(image_path))
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
+ image = Gtk.Image.new_from_pixbuf(pixbuf)
+ button.set_image(image)
return self.create_window(button)
def create_image_window(self, image_path):
- image = gtk.image_new_from_file(image_path)
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
+ image = Gtk.Image.new_from_pixbuf(pixbuf)
return self.create_window(image)
class XUtilsTest(XUtilsTestBase):
def test_type(self):
- entry = gtk.Entry()
+ entry = Gtk.Entry()
window = self.create_window(entry)
try:
window.present()
@@ -95,51 +98,36 @@
self.flush_gtk()
- resolution = gtk.gdk.get_default_root_window().get_size()
-
- window_x, window_y = window.get_child().window.get_position()
- window_width, window_height = window.get_child().window.get_size()
-
+ width = Gdk.get_default_root_window().get_width()
+ height = Gdk.get_default_root_window().get_height()
+
+ window_x, window_y = window.get_child().get_window().get_position()
+
+ time.sleep(2)
big_screenshot = xutils.take_screenshot()
- small_screenshot = xutils.take_screenshot(window_x, window_y,
- window_width, window_height)
window.destroy()
self.flush_gtk()
# Check the basic attributes set
self.assertEquals(big_screenshot.name, "screenshot")
- self.assertEquals(big_screenshot.width, resolution[0])
- self.assertEquals(big_screenshot.height, resolution[1])
-
- self.assertEquals(small_screenshot.name, "screenshot")
- self.assertEquals(small_screenshot.width, window_width)
- self.assertEquals(small_screenshot.height, window_height)
+ self.assertEquals(big_screenshot.width, width)
+ self.assertEquals(big_screenshot.height, height)
# Now verify the actual images taken.
finder = OpenCVFinder()
big_match = finder.find(big_screenshot, red_square)
- small_match = finder.find(small_screenshot, red_square)
self.assertEquals(big_match.image, red_square)
self.assertTrue(big_match.similarity > 0.95, big_match.similarity)
- self.assertEquals(small_match.image, red_square)
- self.assertTrue(small_match.similarity > 0.95, small_match.similarity)
-
# The match we found in the big screenshot should be in the same
# position as the window we created. Note that this may fail if
# you have the image opened elsewhere. ;-)
self.assertEquals(big_match.x, window_x)
self.assertEquals(big_match.y, window_y)
- # With the small match, it should be in the origin, since the
- # screenshot was taken on the precise area.
- self.assertEquals(small_match.x, 0)
- self.assertEquals(small_match.y, 0)
-
-
class XUtilsButtonTest(XUtilsTestBase):
@@ -153,13 +141,13 @@
self.button_dclicked = False
def clicked(widget, event):
- if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
+ if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
self.button_dclicked = True
- elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+ elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
self.button_clicked = True
elif event.button == 3:
self.button_rclicked = True
-
+
def entered(widget):
self.button_hovered = True
@@ -172,8 +160,9 @@
self.window.destroy()
def get_button_center(self):
- button_x, button_y = self.button.window.get_position()
- button_width, button_height = self.button.window.get_size()
+ button_x, button_y = self.button.get_window().get_position()
+ button_width = self.button.get_window().get_width()
+ button_height = self.button.get_window().get_height()
return (button_x + button_width//2, button_y + button_height//2)
self.window.destroy()
=== modified file 'xpresser/xp.py'
--- xpresser/xp.py 2011-12-09 18:35:34 +0000
+++ xpresser/xp.py 2012-07-05 13:34:35 +0000
@@ -59,7 +59,7 @@
"""Click on the position specified by the provided arguments.
The following examples show valid ways of specifying the position:
-
+
xp.click("image-name")
xp.click(image_match)
xp.click(x, y)
@@ -70,7 +70,7 @@
"""Right-click on the position specified by the provided arguments.
The following examples show valid ways of specifying the position:
-
+
xp.right_click("image-name")
xp.right_click(image_match)
xp.right_click(x, y)
@@ -80,18 +80,18 @@
def double_click(self, *args):
'''Double clicks over the position specified by arguments
- The following examples show valid ways of specifying te position:
+ The following examples show valid ways of specifying the position:
xp.double_click("image-name")
xp.double_click(image_match)
xp.double_click(x, y)
'''
xutils.double_click(*self._compute_focus_point(args))
-
+
def hover(self, *args):
"""Hover over the position specified by the provided arguments.
The following examples show valid ways of specifying the position:
-
+
xp.hover("image-name")
xp.hover(image_match)
xp.hover(x, y)
=== modified file 'xpresser/xutils.py'
--- xpresser/xutils.py 2011-12-09 18:35:34 +0000
+++ xpresser/xutils.py 2012-07-05 13:34:35 +0000
@@ -17,15 +17,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+import pyatspi
+import SimpleCV
from xpresser.image import Image
-
-import pyatspi
-import gtk
-
-import warnings
-
-# pygtk is using a deprecated method from numpy in get_pixels_array().
-warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
+from tempfile import NamedTemporaryFile
+from gi.repository import Gdk
def click(x, y):
@@ -42,21 +38,14 @@
def type(string):
for char in string:
- keyval = gtk.gdk.unicode_to_keyval(ord(char))
- pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
-
-
-def take_screenshot(x=0, y=0, width=None, height=None):
- window = gtk.gdk.get_default_root_window()
- if not (width and height):
- size = window.get_size()
- if not width:
- width = size[0]
- if not height:
- height = size[1]
- pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
- pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
- x, y, 0, 0, width, height)
- array = pixbuf.get_pixels_array()
- return Image("screenshot", array=array,
- width=array.shape[1], height=array.shape[0])
+ keyval = Gdk.unicode_to_keyval(ord(char))
+ pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
+
+def take_screenshot():
+ window = Gdk.get_default_root_window()
+ surface = Gdk.cairo_create(window).get_target()
+ with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f:
+ surface.write_to_png(f.name)
+ opencv_image = SimpleCV.Image(f.name)
+ return Image("screenshot", array=opencv_image,
+ width=opencv_image.width, height=opencv_image.height)
Follow ups