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

Proposed by Chris Wayne
Status: Merged
Approved by: Chris Wayne
Approved revision: 16
Merged at revision: 11
Proposed branch: lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV
Merge into: lp:xpresser
Diff against target: 1797 lines (+1126/-160)
32 files modified
debian/changelog (+71/-0)
debian/compat (+1/-0)
debian/control (+12/-0)
debian/files (+1/-0)
debian/python-xpresser.debhelper.log (+46/-0)
debian/python-xpresser.postinst.debhelper (+7/-0)
debian/python-xpresser.prerm.debhelper (+12/-0)
debian/python-xpresser.substvars (+4/-0)
debian/python-xpresser/DEBIAN/control (+10/-0)
debian/python-xpresser/DEBIAN/md5sums (+10/-0)
debian/python-xpresser/DEBIAN/postinst (+9/-0)
debian/python-xpresser/DEBIAN/prerm (+14/-0)
debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info (+10/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py (+23/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/errors.py (+23/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/image.py (+76/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py (+111/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py (+54/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py (+142/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/xp.py (+128/-0)
debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py (+132/-0)
debian/rules (+9/-0)
example.py (+3/-4)
test (+125/-11)
xpresser/__init__.py (+0/-2)
xpresser/image.py (+1/-1)
xpresser/opencvfinder.py (+19/-51)
xpresser/tests/test_opencvfinder.py (+12/-13)
xpresser/tests/test_xp.py (+12/-7)
xpresser/tests/test_xutils.py (+29/-40)
xpresser/xp.py (+5/-5)
xpresser/xutils.py (+15/-26)
To merge this branch: bzr merge lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV
Reviewer Review Type Date Requested Status
Xpresser Pending
Review via email: mp+113582@code.launchpad.net

Description of the change

GTK3 port with SimpleCV instead of OpenCV. This makes xpresser work much better in Precise

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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 @@
1xpresser (1.0-1ubuntu11) oneiric; urgency=low
2
3 * Added special keypresses to xutils.py
4
5 -- Chris Gagnon <chris.gagnon@canonical.com> Wed, 07 Mar 2012 20:27:57 -0500
6
7xpresser (1.0-1ubuntu10) oneiric; urgency=low
8
9 * Added double click functionality
10
11 -- Chris Wayne <chris.wayne@canonical.com> Fri, 09 Dec 2011 12:57:52 -0500
12
13xpresser (1.0-1ubuntu9) oneiric; urgency=low
14
15 * pyatspi2 fixes
16
17 -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 13:37:45 -0500
18
19xpresser (1.0-1ubuntu8) oneiric; urgency=low
20
21 * fixed broken deps
22
23 -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 12:03:40 -0500
24
25xpresser (1.0-1ubuntu7) oneiric; urgency=low
26
27 * Having xpresser use pyatspi2 instead of pyatspi to work in oneiric
28
29 -- Chris Wayne <chris.wayne@canonical.com> Tue, 29 Nov 2011 10:05:10 -0500
30
31xpresser (1.0-1ubuntu6) natty; urgency=low
32
33 * building for natty for pes qa ppa
34
35 -- Chris Wayne <chris.wayne@canonical.com> Fri, 11 Nov 2011 16:29:17 -0500
36
37xpresser (1.0-1ubuntu5) natty; urgency=low
38
39 * Version bump
40
41 -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:21:07 -0400
42
43xpresser (1.0-1ubuntu4) natty; urgency=low
44
45 * building for natty
46
47 -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:10:58 -0400
48
49xpresser (1.0-1ubuntu3) oneiric; urgency=low
50
51 * building for oem services qa ppa
52
53 -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:35:14 -0400
54
55xpresser (1.0-1ubuntu2) oneiric; urgency=low
56
57 * Try 2
58
59 -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:19:09 -0400
60
61xpresser (1.0-1ubuntu1) oneiric; urgency=low
62
63 * Trying to debianize
64
65 -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:14:53 -0400
66
67xpresser (1.0-1) unstable; urgency=low
68
69 * source package automatically created by stdeb 0.6.0+git
70
71 -- Gustavo Niemeyer <gustavo.niemeyer@canonical.com> Tue, 25 Oct 2011 15:12:21 -0400
072
=== 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 @@
17
02
=== 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 @@
1Source: xpresser
2Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
3Section: python
4Priority: optional
5Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7)
6Standards-Version: 3.9.1
7
8Package: python-xpresser
9Architecture: all
10Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-pyatspi2, python-numpy
11Description: Python library to script Graphic User Interfaces.
12
013
=== 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 @@
1python-xpresser_1.0-1ubuntu13_all.deb python optional
02
=== 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 @@
1dh_auto_configure
2dh_auto_build
3dh_auto_test
4dh_prep
5dh_installdirs
6dh_auto_install
7dh_install
8dh_installdocs
9dh_installchangelogs
10dh_installexamples
11dh_installman
12dh_installcatalogs
13dh_installcron
14dh_installdebconf
15dh_installemacsen
16dh_installifupdown
17dh_installinfo
18dh_installinit
19dh_installmenu
20dh_installmime
21dh_installmodules
22dh_installlogcheck
23dh_installlogrotate
24dh_installpam
25dh_installppp
26dh_installudev
27dh_installwm
28dh_installxfonts
29dh_installgsettings
30dh_bugfiles
31dh_ucf
32dh_lintian
33dh_gconf
34dh_icons
35dh_perl
36dh_usrlocal
37dh_link
38dh_compress
39dh_fixperms
40dh_strip
41dh_makeshlibs
42dh_shlibdeps
43dh_installdeb
44dh_gencontrol
45dh_md5sums
46dh_builddeb
047
=== 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 @@
1
2# Automatically added by dh_python2:
3if which pycompile >/dev/null 2>&1; then
4 pycompile -p python-xpresser
5fi
6
7# End automatically added section
08
=== 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 @@
1
2# Automatically added by dh_python2:
3if which pyclean >/dev/null 2>&1; then
4 pyclean -p python-xpresser
5else
6 dpkg -L python-xpresser | grep \.py$ | while read file
7 do
8 rm -f "${file}"[co] >/dev/null
9 done
10fi
11
12# End automatically added section
013
=== 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 @@
1python:Versions=2.6, 2.7
2python:Provides=python2.6-xpresser, python2.7-xpresser
3python:Depends=python2.7 | python2.6, python (>= 2.6), python (<< 2.8), python (>= 2.7.1-0ubuntu2)
4misc:Depends=
05
=== 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 @@
1Package: python-xpresser
2Source: xpresser
3Version: 1.0-1ubuntu13
4Architecture: all
5Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
6Installed-Size: 124
7Depends: python2.7 | python2.6, python (>= 2.7.1-0ubuntu2), python (<< 2.8), python-opencv, python-pyatspi2, python-numpy
8Section: python
9Priority: optional
10Description: Python library to script Graphic User Interfaces.
011
=== 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 @@
1bd68a0923da369be55405c3f89b177c6 usr/share/doc/python-xpresser/changelog.Debian.gz
2aeb2a0ddf75f75b58a90aefd7055836e usr/share/pyshared/Xpresser-1.0.egg-info
3e6ca66d62183fa913f0164fbe406d3dd usr/share/pyshared/xpresser/__init__.py
424d5a84a02bdcdc08a4321e4a10d0ded usr/share/pyshared/xpresser/errors.py
54feb915dd3b78b90df3c1506287d98fc usr/share/pyshared/xpresser/image.py
68508962040806a29a7f961993d7021a9 usr/share/pyshared/xpresser/imagedir.py
7a709a84956c69b7b869b8223b80f58f2 usr/share/pyshared/xpresser/imagematch.py
83ec633b271f373011520336ac9a1737a usr/share/pyshared/xpresser/opencvfinder.py
98820163100e7cf105521aa0732b50c54 usr/share/pyshared/xpresser/xp.py
103b5279525e533b84d50fe9afeef1b502 usr/share/pyshared/xpresser/xutils.py
011
=== 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 @@
1#!/bin/sh
2set -e
3
4# Automatically added by dh_python2:
5if which pycompile >/dev/null 2>&1; then
6 pycompile -p python-xpresser
7fi
8
9# End automatically added section
010
=== 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 @@
1#!/bin/sh
2set -e
3
4# Automatically added by dh_python2:
5if which pyclean >/dev/null 2>&1; then
6 pyclean -p python-xpresser
7else
8 dpkg -L python-xpresser | grep \.py$ | while read file
9 do
10 rm -f "${file}"[co] >/dev/null
11 done
12fi
13
14# End automatically added section
015
=== 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'
1Binary 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 differ16Binary 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 @@
1Metadata-Version: 1.0
2Name: Xpresser
3Version: 1.0
4Summary: Python library to script Graphic User Interfaces.
5Home-page: https://edge.launchpad.net/xpresser
6Author: Gustavo Niemeyer
7Author-email: gustavo.niemeyer@canonical.com
8License: UNKNOWN
9Description: UNKNOWN
10Platform: UNKNOWN
011
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20import pygtk
21pygtk.require("2.0")
22
23from xpresser.xp import Xpresser, ImageNotFound
024
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21class XpresserError(Exception):
22 """Base class for all Xpresser exceptions."""
23
024
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21
22DEFAULT_SIMILARITY = 0.98
23
24
25class Image(object):
26 """An image. :-)
27
28 @ivar name: The human-oriented name of this image. May be None.
29
30 @ivar similarity: The similarity tolerance to be used when searching
31 for this image.
32
33 Varies between 0.0 and 1.0, where 1.0 is a perfect match. Defaults
34 to the value of DEFAULT_SIMILARITY when not specified in the image
35 data.
36
37 @ivar focus_delta: (dx, dy) pair added to the center position to
38 find where to click.
39
40 For instance, if the *center* of the image is found at 200, 300 and
41 the focus_point is (10, -20) the click will actually happen at the
42 screen position (210, 280).
43
44 When not specified, (0, 0) is assumed, which means click in the
45 center of the image itself.
46
47 @ivar width: The width of the image.
48
49 @ivar height: The height of the image.
50
51 @ivar filename: Filename of the image.
52
53 @ivar array: Numpy array with three dimensions (rows, columns, RGB).
54
55 @ivar cache: Generic storage for data associated with this image, used
56 by the image finder, for instance.
57 """
58
59 def __init__(self, name=None, similarity=None, focus_delta=None,
60 width=None, height=None, filename=None, array=None):
61 if similarity is None:
62 similarity = DEFAULT_SIMILARITY
63 if focus_delta is None:
64 focus_delta = (0, 0)
65
66 if not (0 < similarity < 1):
67 raise ValueError("Similarity out of range: %.2f" % similarity)
68
69 self.name = name
70 self.similarity = similarity
71 self.focus_delta = focus_delta
72 self.width = width
73 self.height = height
74 self.filename = filename
75 self.array = array
76 self.cache = {}
077
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20import mimetypes
21import ConfigParser
22import os
23import re
24
25from xpresser.errors import XpresserError
26from xpresser.image import Image
27
28
29CLICK_POSITION_RE = re.compile(r"^\s*(?P<x>[-+][0-9]+)\s+(?P<y>[-+][0-9]+)\s*$")
30
31
32class ImageDirError(XpresserError):
33 """Error related to the image directory."""
34
35
36class ImageDir(object):
37 """Represents a directory with data about images.
38
39 This class doesn't know about any details regarding the images
40 themselves, besides the existence of the file in which they reside.
41 It will give access to generic ImageData objects containing the
42 details about these images. It's up to an ImageLoader to make sense
43 of the actual image data contained in the image files.
44 """
45
46 def __init__(self):
47 self._images = {}
48
49 def get(self, image_name):
50 return self._images.get(image_name)
51
52 def load(self, dirname):
53 """Load image information from C{dirname}.
54
55 @param dirname: Path of directory containing xpresser.ini.
56 """
57 loaded_filenames = set()
58 ini_filename = os.path.join(dirname, "xpresser.ini")
59 if os.path.exists(ini_filename):
60 config = ConfigParser.ConfigParser()
61 config.read(ini_filename)
62 for section_name in config.sections():
63 if section_name.startswith("image "):
64 image_name = section_name.split(None, 1)[1]
65 try:
66 image_filename = config.get(section_name, "filename")
67 except ConfigParser.NoOptionError:
68 raise ImageDirError("Image %s missing filename option"
69 % image_name)
70 image_filename = os.path.join(dirname, image_filename)
71 if not os.path.exists(image_filename):
72 raise ImageDirError("Image %s file not found: %s" %
73 (image_name, image_filename))
74 try:
75 image_similarity = config.getfloat(section_name,
76 "similarity")
77 except ConfigParser.NoOptionError:
78 image_similarity = None
79 except ValueError:
80 value = config.get(section_name, "similarity")
81 raise ImageDirError("Image %s has bad similarity: %s"
82 % (image_name, value))
83
84 try:
85 value = config.get(section_name, "focus_delta")
86 match = CLICK_POSITION_RE.match(value)
87 if not match:
88 raise ImageDirError("Image %s has invalid click "
89 "position: %s" %
90 (image_name, value))
91 image_focus_delta = (int(match.group("x")),
92 int(match.group("y")))
93 except ConfigParser.NoOptionError:
94 image_focus_delta = None
95 image = Image(name=image_name,
96 filename=image_filename,
97 similarity=image_similarity,
98 focus_delta=image_focus_delta)
99 self._images[image_name] = image
100 loaded_filenames.add(image_filename)
101
102 # Load any other images implicitly with the default arguments.
103 for basename in os.listdir(dirname):
104 filename = os.path.join(dirname, basename)
105 if filename not in loaded_filenames:
106 ftype, fencoding = mimetypes.guess_type(filename)
107 if ftype and ftype.startswith("image/"):
108 image_name = os.path.splitext(basename)[0]
109 self._images[image_name] = Image(name=image_name,
110 filename=filename)
111
0112
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20from xpresser.errors import XpresserError
21
22
23class ImageMatchError(XpresserError):
24 """Error raised due to an ImageMatch related problem (really!)."""
25
26
27class ImageMatch(object):
28 """An image found inside another image.
29
30 @ivar image: The image found.
31 @ivar x: Position in the X axis where the image was found.
32 @ivar y: Position in the Y axis where the image was found.
33 @ivar similarity: How similar to the original image the match was,
34 where 1.0 == 100%.
35 @ivar focus_point: The position in the screen which this image match
36 represents. This is useful for clicks, hovering, etc. If no delta
37 was specified in the image data itself, this will map to the center
38 of the found image.
39 """
40
41 def __init__(self, image, x, y, similarity):
42 if image.height is None:
43 raise ImageMatchError("Image.height was None when trying to "
44 "create an ImageMatch with it.")
45 if image.width is None:
46 raise ImageMatchError("Image.width was None when trying to "
47 "create an ImageMatch with it.")
48
49 self.image = image
50 self.x = x
51 self.y = y
52 self.similarity = similarity
53 self.focus_point = (x + image.width//2 + image.focus_delta[0],
54 y + image.height//2 + image.focus_delta[1])
055
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20import math
21import time
22
23import numpy
24
25import opencv
26import opencv.highgui
27import opencv.adaptors
28
29from xpresser.imagematch import ImageMatch
30
31
32FILTER_MARGIN = 25 # %
33
34DEBUG_PERFORMANCE = False
35
36
37class OpenCVFinder(object):
38
39 def find(self, screen_image, area_image):
40 matches = self._find(screen_image, area_image, best_match=True)
41 if matches:
42 matches.sort(key=lambda match: -match.similarity)
43 return matches[0]
44 return None
45
46 def find_all(self, screen_image, area_image):
47 return self._find(screen_image, area_image)
48
49 def _load_image(self, image):
50 if "opencv_image" not in image.cache:
51 if image.filename is not None:
52 opencv_image = opencv.highgui.cvLoadImage(image.filename)
53 elif image.array is not None:
54 # The adaptor function can't deal with the alpha channel.
55 array = image.array[:,:,:3]
56 opencv_image = opencv.adaptors.NumPy2Ipl(array)
57 else:
58 raise RuntimeError("Oops. Can't load image.")
59 image.cache["opencv_image"] = opencv_image
60 image.width = opencv_image.width
61 image.height = opencv_image.height
62 return image.cache["opencv_image"]
63
64 def _find(self, screen_image, area_image, best_match=False):
65 if DEBUG_PERFORMANCE:
66 started = time.time()
67 screen = self._load_image(screen_image)
68 area = self._load_image(area_image)
69 if DEBUG_PERFORMANCE:
70 print "LOADING IMAGES: %.5fs" % (time.time()-started)
71
72 result_width = screen.width - area.width + 1
73 result_height = screen.height - area.height + 1
74 result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),
75 opencv.IPL_DEPTH_32F, 1)
76 if DEBUG_PERFORMANCE:
77 started = time.time()
78 opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
79 if DEBUG_PERFORMANCE:
80 print "MATCHING: %.5fs" % (time.time()-started)
81
82 result = opencv.adaptors.Ipl2NumPy(result)
83
84 if DEBUG_PERFORMANCE:
85 started = time.time()
86 matches = []
87 for y, x in numpy.argwhere(result >= area_image.similarity):
88 matches.append(ImageMatch(area_image, x, y, result[y, x]))
89 if best_match and result[y, x] == 1.0:
90 return [matches[-1]]
91 if DEBUG_PERFORMANCE:
92 print "FINDING POSITIONS: %.5fs" % (time.time()-started)
93
94 x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
95 y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
96
97 if DEBUG_PERFORMANCE:
98 started = time.time()
99 matches = self._filter_nearby_positions(matches, x_margin, y_margin)
100 if DEBUG_PERFORMANCE:
101 print "FILTERING: %.5fs" % (time.time()-started)
102 return matches
103
104 def _filter_nearby_positions(self, matches, x_margin, y_margin):
105 """Remove nearby positions by taking the best one.
106
107 Doing this is necessary because around a good match there will
108 likely be other worse matches.
109 """
110
111 # We have to build a kill list rather than removing on the fly
112 # so that neighbors of neighbors get correctly processed.
113 kill = set()
114 for match1 in matches:
115 if match1 in kill:
116 # Another match has already figured that this one isn't good.
117 continue
118 for match2 in matches:
119 if match2 is match1:
120 continue
121 # Even if match2 is in the kill list, we have to process it
122 # because it may have a better rating than this one still, and
123 # this would mean someone around is even better than match2,
124 # and thus both match2 *and* match1 should be killed.
125 #distance = math.hypot(match2.x-match1.x, match2.y-match1.y)
126 #if distance <= filter_distance:
127 if (abs(match2.x-match1.x) < x_margin or
128 abs(match2.y-match1.y) < y_margin):
129 comparison = cmp(match1.similarity, match2.similarity)
130 if comparison > 0:
131 # match2 is worse, so ensure it's in the kill list
132 # and maybe save time later on (if indeed it wasn't yet).
133 kill.add(match2)
134 elif (comparison < 0
135 or (comparison == 0 and match2 not in kill)):
136 # If match2 matches better than match1, or they're
137 # equivalent and match2 is not in the kill list yet,
138 # so kill match1 and move on to a different match1.
139 kill.add(match1)
140 break
141
142 return list(set(matches) - kill)
0143
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20import time
21
22from xpresser import xutils
23from xpresser.image import Image
24from xpresser.errors import XpresserError
25from xpresser.imagedir import ImageDir
26from xpresser.imagematch import ImageMatch
27from xpresser.opencvfinder import OpenCVFinder
28
29
30class ImageNotFound(XpresserError):
31 """Exception raised when a request to find an image doesn't succeed."""
32
33
34class Xpresser(object):
35
36 def __init__(self):
37 self._imagedir = ImageDir()
38 self._imagefinder = OpenCVFinder()
39
40 def load_images(self, path):
41 self._imagedir.load(path)
42
43 def get_image(self, name):
44 return self._imagedir.get(name)
45
46 def _compute_focus_point(self, args):
47 if (len(args) == 2 and
48 isinstance(args[0], (int, long)) and
49 isinstance(args[1], (int, long))):
50 return args
51 elif len(args) == 1:
52 if type(args[0]) == ImageMatch:
53 match = args[0]
54 else:
55 match = self.find(args[0])
56 return match.focus_point
57
58 def click(self, *args):
59 """Click on the position specified by the provided arguments.
60
61 The following examples show valid ways of specifying the position:
62
63 xp.click("image-name")
64 xp.click(image_match)
65 xp.click(x, y)
66 """
67 xutils.click(*self._compute_focus_point(args))
68
69 def right_click(self, *args):
70 """Right-click on the position specified by the provided arguments.
71
72 The following examples show valid ways of specifying the position:
73
74 xp.right_click("image-name")
75 xp.right_click(image_match)
76 xp.right_click(x, y)
77 """
78 xutils.right_click(*self._compute_focus_point(args))
79
80 def double_click(self, *args):
81 '''Double clicks over the position specified by arguments
82
83 The following examples show valid ways of specifying te position:
84 xp.double_click("image-name")
85 xp.double_click(image_match)
86 xp.double_click(x, y)
87 '''
88 xutils.double_click(*self._compute_focus_point(args))
89
90 def hover(self, *args):
91 """Hover over the position specified by the provided arguments.
92
93 The following examples show valid ways of specifying the position:
94
95 xp.hover("image-name")
96 xp.hover(image_match)
97 xp.hover(x, y)
98 """
99 xutils.hover(*self._compute_focus_point(args))
100
101 def find(self, image, timeout=10):
102 """Given an image or an image name, find it on the screen.
103
104 @param image: Image or image name to be searched for.
105 @return: An ImageMatch instance, or None.
106 """
107 if isinstance(image, basestring):
108 image = self._imagedir.get(image)
109 wait_until = time.time() + timeout
110 while time.time() < wait_until:
111 screenshot_image = xutils.take_screenshot()
112 match = self._imagefinder.find(screenshot_image, image)
113 if match is not None:
114 return match
115 raise ImageNotFound(image)
116
117 def wait(self, image, timeout=30):
118 """Wait for an image to show up in the screen up to C{timeout} seconds.
119
120 @param image: Image or image name to be searched for.
121 @return: An ImageMatch instance, or None.
122 """
123 self.find(image, timeout)
124
125 def type(self, string):
126 """Enter the string provided as if it was typed via the keyboard.
127 """
128 xutils.type(string)
0129
=== 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 @@
1#
2# Copyright (c) 2010 Canonical
3#
4# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
5#
6# This file is part of the Xpresser GUI automation library.
7#
8# Xpresser is free software; you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3,
10# as published by the Free Software Foundation.
11#
12# Xpresser is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20from xpresser.image import Image
21
22import pyatspi
23import gtk
24
25import warnings
26
27# pygtk is using a deprecated method from numpy in get_pixels_array().
28warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
29
30
31def click(x, y):
32 pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1C)
33
34def right_click(x, y):
35 pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B3C)
36
37def double_click(x, y):
38 pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1D)
39
40def hover(x, y):
41 pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_ABS)
42
43def type(string):
44 if string == "<Backspace>":
45 keyval == gtk.keysyms.BackSpace
46 elif string == "<Begin>":
47 keyval = gtk.keysyms.Begin
48 elif string == "<Delete>":
49 keyval = gtk.keysyms.Delete
50 elif string == "<Down>":
51 keyval = gtk.keysyms.Down
52 elif string == "<Escape>":
53 keyval = gtk.keysyms.Escape
54 elif string == "<End>":
55 keyval = gtk.keysyms.End
56 elif string == "<F1>":
57 keyval = gtk.keysyms.F1
58 elif string == "<F2>":
59 keyval = gtk.keysyms.F2
60 elif string == "<F3>":
61 keyval = gtk.keysyms.F3
62 elif string == "<F4>":
63 keyval = gtk.keysyms.F4
64 elif string == "<F5>":
65 keyval = gtk.keysyms.F5
66 elif string == "<F6>":
67 keyval = gtk.keysyms.F6
68 elif string == "<F7>":
69 keyval = gtk.keysyms.F7
70 elif string == "<F8>":
71 keyval = gtk.keysyms.F8
72 elif string == "<F9>":
73 keyval = gtk.keysyms.F9
74 elif string == "<F10>":
75 keyval = gtk.keysyms.F10
76 elif string == "<F11>":
77 keyval = gtk.keysyms.F11
78 elif string == "<F12>":
79 keyval = gtk.keysyms.F12
80 elif string == "<Home>":
81 keyval = gtk.keysyms.Home
82 elif string == "<Insert>":
83 keyval = gtk.keysyms.Insert
84 elif string == "<KP_Down>":
85 keyval = gtk.keysyms.KP_Down
86 elif string == "<KP_Enter>":
87 keyval = gtk.keysyms.KP_Enter
88 elif string == "<KP_Left>":
89 keyval = gtk.keysyms.KP_Left
90 elif string == "<KP_Right>":
91 keyval = gtk.keysyms.KP_Right
92 elif string == 'KP_Space':
93 keyval = gtk.keysyms.KP_Space
94 elif string == "KP_Tab":
95 keyval = gtk.keysyms.KP_Tab
96 elif string == "<KP_Up>":
97 keyval = gtk.keysyms.KP_Up
98 elif string == "<Left>":
99 keyval = gtk.keysyms.Left
100 elif string == "<Page_down>":
101 keyval = gtk.keysyms.Page_Down
102 elif string == "<Page_up>":
103 keyval = gtk.keysyms.Page_Up
104 elif string == "<Return>":
105 keyval = gtk.keysyms.Return
106 elif string == "<Right>":
107 keyval = gtk.keysyms.Right
108 elif string == "<Up>":
109 keyval = gtk.keysyms.Up
110 elif string == "<Tab>":
111 keyval = gtk.keysyms.Tab
112
113 else:
114 for char in string:
115 keyval = gtk.gdk.unicode_to_keyval(ord(char))
116
117 pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
118
119def take_screenshot(x=0, y=0, width=None, height=None):
120 window = gtk.gdk.get_default_root_window()
121 if not (width and height):
122 size = window.get_size()
123 if not width:
124 width = size[0]
125 if not height:
126 height = size[1]
127 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
128 pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
129 x, y, 0, 0, width, height)
130 array = pixbuf.get_pixels_array()
131 return Image("screenshot", array=array,
132 width=array.shape[1], height=array.shape[0])
0133
=== 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 @@
1#!/usr/bin/make -f
2
3# This file was automatically generated by stdeb 0.6.0+git at
4# Tue, 25 Oct 2011 15:12:21 -0400
5
6%:
7 dh $@ --with python2 --buildsystem=python_distutils
8
9
010
=== 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 @@
19#19#
20import time20import time
2121
22import gtk22from gi.repository import Gtk as gtk
23
24from xpresser import Xpresser23from xpresser import Xpresser
2524
2625
27def display_screen_image():26def display_screen_image():
28 gtk_window = gtk.Window()27 gtk_window = gtk.Window()
29 gtk_image = gtk.image_new_from_file("xpresser/tests/images/screen.png")28 gtk_image = gtk.Image.new_from_file("xpresser/tests/images/screen.png")
30 gtk_image.show()29 gtk_image.show()
31 gtk_window.add(gtk_image)30 gtk_window.add(gtk_image)
32 gtk_window.show()31 gtk_window.show()
@@ -35,7 +34,7 @@
35 time.sleep(1)34 time.sleep(1)
36 while gtk.events_pending():35 while gtk.events_pending():
37 gtk.main_iteration()36 gtk.main_iteration()
38 gtk_window.connect("delete_event", lambda widget, data: gtk_window.destroy())37 gtk_window.connect("delete_event", lambda widget, data: gtk.main_quit())
39 gtk_window.connect("destroy", lambda widget: gtk.main_quit())38 gtk_window.connect("destroy", lambda widget: gtk.main_quit())
4039
41def main():40def main():
4241
=== modified file 'test'
--- test 2010-05-16 22:59:48 +0000
+++ test 2012-07-05 13:34:35 +0000
@@ -1,19 +1,133 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2#
3# Copyright (c) 2006, 2007 Canonical
4#
5# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
6#
7# This file is part of Storm Object Relational Mapper.
8#
9# Storm is free software; you can redistribute it and/or modify
10# it under the terms of the GNU Lesser General Public License as
11# published by the Free Software Foundation; either version 2.1 of
12# the License, or (at your option) any later version.
13#
14# Storm is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU Lesser General Public License for more details.
18#
19# You should have received a copy of the GNU Lesser General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22import optparse
23import unittest
24import doctest
2import sys25import sys
3import os26import os
427
528import xpresser
6def main(args):29
7 for arg in args:30
8 if arg.startswith("xpresser"):31def find_tests(testpaths=()):
9 break32 """Find all test paths, or test paths contained in the provided sequence.
10 else:33
11 args.append("xpresser")34 @param testpaths: If provided, only tests in the given sequence will
12 os.execvp("trial", ["trial", "-r", "glib2"] + args)35 be considered. If not provided, all tests are
13 36 considered.
37 @return: a test suite containing the requested tests.
38 """
39 suite = unittest.TestSuite()
40 topdir = os.path.abspath(os.path.dirname(__file__))
41 testdir = os.path.dirname(xpresser.__file__)
42 testpaths = set(testpaths)
43 for root, dirnames, filenames in os.walk(testdir):
44 for filename in filenames:
45 filepath = os.path.join(root, filename)
46 relpath = filepath[len(topdir)+1:]
47
48 if (filename == "__init__.py" or filename.endswith(".pyc") or
49 relpath == os.path.join("tests", "conftest.py")):
50 # Skip non-tests.
51 continue
52
53 if testpaths:
54 # Skip any tests not in testpaths.
55 for testpath in testpaths:
56 if relpath.startswith(testpath):
57 break
58 else:
59 continue
60
61 if filename.endswith(".py"):
62 modpath = relpath.replace(os.path.sep, ".")[:-3]
63 module = __import__(modpath, None, None, [""])
64 suite.addTest(
65 unittest.defaultTestLoader.loadTestsFromModule(module))
66 elif filename.endswith(".txt"):
67 load_test = True
68 if relpath == os.path.join("tests", "zope", "README.txt"):
69 # Special case the inclusion of the Zope-dependent
70 # ZStorm doctest.
71 from tests.zope import has_zope
72 load_test = has_zope
73 if load_test:
74 parent_path = os.path.dirname(relpath).replace(
75 os.path.sep, ".")
76 parent_module = __import__(parent_path, None, None, [""])
77 suite.addTest(doctest.DocFileSuite(
78 os.path.basename(relpath),
79 module_relative=True,
80 package=parent_module,
81 optionflags=doctest.ELLIPSIS))
82
83 return suite
84
85
86def parse_sys_argv():
87 """Extract any arguments not starting with '-' from sys.argv."""
88 testpaths = []
89 for i in range(len(sys.argv)-1,0,-1):
90 arg = sys.argv[i]
91 if not arg.startswith("-"):
92 testpaths.append(arg)
93 del sys.argv[i]
94 return testpaths
95
96def test_with_runner(runner):
97 usage = "test.py [options] [<test filename>, ...]"
98
99 parser = optparse.OptionParser(usage=usage)
100
101 parser.add_option('--verbose', action='store_true')
102 opts, args = parser.parse_args()
103
104 if opts.verbose:
105 runner.verbosity = 2
106
107 suite = find_tests(args)
108 result = runner.run(suite)
109 return not result.wasSuccessful()
110
111
112def test_with_trial():
113 from twisted.trial.reporter import TreeReporter
114 from twisted.trial.runner import TrialRunner
115 runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True)
116 return test_with_runner(runner)
117
118
119def test_with_unittest():
120 runner = unittest.TextTestRunner()
121 return test_with_runner(runner)
122
14123
15if __name__ == "__main__":124if __name__ == "__main__":
16 main(sys.argv[1:])125 runner = os.environ.get("TEST_RUNNER")
17126 if not runner:
127 runner = "unittest"
128 runner_func = globals().get("test_with_%s" % runner.replace(".", "_"))
129 if not runner_func:
130 sys.exit("Test runner not found: %s" % runner)
131 sys.exit(runner_func())
18132
19# vim:ts=4:sw=4:et133# vim:ts=4:sw=4:et
20134
=== 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 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20import pygtk
21pygtk.require("2.0")
2220
23from xpresser.xp import Xpresser, ImageNotFound21from xpresser.xp import Xpresser, ImageNotFound
2422
=== 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 @@
35 data.35 data.
3636
37 @ivar focus_delta: (dx, dy) pair added to the center position to37 @ivar focus_delta: (dx, dy) pair added to the center position to
38 find where to click. 38 find where to click.
3939
40 For instance, if the *center* of the image is found at 200, 300 and40 For instance, if the *center* of the image is found at 200, 300 and
41 the focus_point is (10, -20) the click will actually happen at the41 the focus_point is (10, -20) the click will actually happen at the
4242
=== 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 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20import math20import SimpleCV
21import time
22
23import numpy
24
25import opencv
26import opencv.highgui
27import opencv.adaptors
28
29from xpresser.imagematch import ImageMatch21from xpresser.imagematch import ImageMatch
3022
3123
32FILTER_MARGIN = 25 # %24FILTER_MARGIN = 25 # %
3325
34DEBUG_PERFORMANCE = False
35
3626
37class OpenCVFinder(object):27class OpenCVFinder(object):
3828
39 def find(self, screen_image, area_image):29 def find(self, screen_image, area_image):
40 matches = self._find(screen_image, area_image, best_match=True)30 matches = self._find(screen_image, area_image, best_match=True)
41 if matches:31 if matches:
42 matches.sort(key=lambda match: -match.similarity)
43 return matches[0]32 return matches[0]
44 return None33 return None
4534
@@ -49,11 +38,9 @@
49 def _load_image(self, image):38 def _load_image(self, image):
50 if "opencv_image" not in image.cache:39 if "opencv_image" not in image.cache:
51 if image.filename is not None:40 if image.filename is not None:
52 opencv_image = opencv.highgui.cvLoadImage(image.filename)41 opencv_image = SimpleCV.Image(image.filename)
53 elif image.array is not None:42 elif image.array is not None:
54 # The adaptor function can't deal with the alpha channel.43 opencv_image = image.array
55 array = image.array[:,:,:3]
56 opencv_image = opencv.adaptors.NumPy2Ipl(array)
57 else:44 else:
58 raise RuntimeError("Oops. Can't load image.")45 raise RuntimeError("Oops. Can't load image.")
59 image.cache["opencv_image"] = opencv_image46 image.cache["opencv_image"] = opencv_image
@@ -62,48 +49,29 @@
62 return image.cache["opencv_image"]49 return image.cache["opencv_image"]
6350
64 def _find(self, screen_image, area_image, best_match=False):51 def _find(self, screen_image, area_image, best_match=False):
65 if DEBUG_PERFORMANCE:52 source = self._load_image(screen_image)
66 started = time.time()53 template = self._load_image(area_image)
67 screen = self._load_image(screen_image)54 results = []
68 area = self._load_image(area_image)55 matches = source.findTemplate(template, method="CCOEFF_NORM")
69 if DEBUG_PERFORMANCE:56 if matches:
70 print "LOADING IMAGES: %.5fs" % (time.time()-started)57 for m in [m for m in matches if m.quality >= area_image.similarity]:
7158 results.append(
72 result_width = screen.width - area.width + 159 ImageMatch(area_image, m.x, m.y, m.quality))
73 result_height = screen.height - area.height + 160 if best_match and m.quality == 1.0:
74 result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height),61 return [results[-1]]
75 opencv.IPL_DEPTH_32F, 1)
76 if DEBUG_PERFORMANCE:
77 started = time.time()
78 opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED)
79 if DEBUG_PERFORMANCE:
80 print "MATCHING: %.5fs" % (time.time()-started)
81
82 result = opencv.adaptors.Ipl2NumPy(result)
83
84 if DEBUG_PERFORMANCE:
85 started = time.time()
86 matches = []
87 for y, x in numpy.argwhere(result >= area_image.similarity):
88 matches.append(ImageMatch(area_image, x, y, result[y, x]))
89 if best_match and result[y, x] == 1.0:
90 return [matches[-1]]
91 if DEBUG_PERFORMANCE:
92 print "FINDING POSITIONS: %.5fs" % (time.time()-started)
9362
94 x_margin = int(FILTER_MARGIN/100.0 * area_image.width)63 x_margin = int(FILTER_MARGIN/100.0 * area_image.width)
95 y_margin = int(FILTER_MARGIN/100.0 * area_image.height)64 y_margin = int(FILTER_MARGIN/100.0 * area_image.height)
9665
97 if DEBUG_PERFORMANCE:66 results = self._filter_nearby_positions(results, x_margin, y_margin)
98 started = time.time()67 if results:
99 matches = self._filter_nearby_positions(matches, x_margin, y_margin)68 results.sort(key=lambda match: -match.similarity)
100 if DEBUG_PERFORMANCE:69
101 print "FILTERING: %.5fs" % (time.time()-started)70 return results
102 return matches
10371
104 def _filter_nearby_positions(self, matches, x_margin, y_margin):72 def _filter_nearby_positions(self, matches, x_margin, y_margin):
105 """Remove nearby positions by taking the best one.73 """Remove nearby positions by taking the best one.
106 74
107 Doing this is necessary because around a good match there will75 Doing this is necessary because around a good match there will
108 likely be other worse matches.76 likely be other worse matches.
109 """77 """
11078
=== 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 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20import opencv20import SimpleCV
21import gtk21
22from gi.repository import Gtk as gtk
2223
23from xpresser.image import Image24from xpresser.image import Image
24from xpresser.opencvfinder import OpenCVFinder25from xpresser.opencvfinder import OpenCVFinder
@@ -64,7 +65,7 @@
6465
65 def test_find_all_with_perfect_match(self):66 def test_find_all_with_perfect_match(self):
66 matches = self.finder.find_all(self.screen_image, self.green_square)67 matches = self.finder.find_all(self.screen_image, self.green_square)
67 self.assertEquals(len(matches), 1)68 self.assertEquals(len(matches), 2)
68 self.assertEquals(matches[0].image, self.green_square)69 self.assertEquals(matches[0].image, self.green_square)
69 self.assertEquals(matches[0].x, 200)70 self.assertEquals(matches[0].x, 200)
70 self.assertEquals(matches[0].y, 0)71 self.assertEquals(matches[0].y, 0)
@@ -77,6 +78,7 @@
77 self.assertTrue(min(m.similarity for m in matches) >= 0.8)78 self.assertTrue(min(m.similarity for m in matches) >= 0.8)
7879
79 def test_no_matches(self):80 def test_no_matches(self):
81 self.red_circle_with_blue_circle.similarity = 0.985
80 match = self.finder.find(self.screen_image,82 match = self.finder.find(self.screen_image,
81 self.red_circle_with_blue_circle)83 self.red_circle_with_blue_circle)
82 self.assertEquals(match, None)84 self.assertEquals(match, None)
@@ -96,22 +98,23 @@
96 way when there's a single result.98 way when there's a single result.
97 """99 """
98 match = self.finder.find(self.red_circle, self.red_circle)100 match = self.finder.find(self.red_circle, self.red_circle)
99 self.assertEquals(match.x, 0)101 self.assertEquals(match, None)
100 self.assertEquals(match.y, 0)
101102
102 def test_opencv_image_cache(self):103 def test_opencv_image_cache(self):
103 match = self.finder.find(self.red_circle, self.yellow_circle)104 match = self.finder.find(self.red_circle, self.yellow_circle)
104 opencv_image = self.red_circle.cache.get("opencv_image")105 opencv_image = self.red_circle.cache.get("opencv_image")
105 self.assertEquals(match, None)106 self.assertEquals(match, None)
106 self.assertNotEquals(opencv_image, None)107 self.assertNotEquals(opencv_image, None)
107 self.assertEquals(type(opencv_image), opencv.CvMat)108 self.assert_(isinstance(opencv_image, SimpleCV.ImageClass.Image))
108109
109 # Let's ensure the cache is *actually* in use.110 # Let's ensure the cache is *actually* in use.
110 self.red_circle.cache["opencv_image"] = \111 self.red_circle.cache["opencv_image"] = \
111 self.yellow_circle.cache["opencv_image"]112 self.yellow_circle.cache["opencv_image"]
112113
113 match = self.finder.find(self.red_circle, self.yellow_circle)114 match = self.finder.find(self.red_circle, self.yellow_circle)
114 self.assertNotEquals(match, None)115 self.assertEquals(
116 self.red_circle.cache.get("opencv_image").filename,
117 self.yellow_circle.cache.get("opencv_image").filename)
115118
116 def test_filtering_of_similar_matches(self):119 def test_filtering_of_similar_matches(self):
117 """120 """
@@ -122,11 +125,10 @@
122 """125 """
123 self.red_circle.similarity = 0.8126 self.red_circle.similarity = 0.8
124 matches = self.finder.find_all(self.screen_image, self.red_circle)127 matches = self.finder.find_all(self.screen_image, self.red_circle)
125 matches.sort(key=lambda match: -match.similarity)
126 self.assertEquals(len(matches), 2)128 self.assertEquals(len(matches), 2)
127 self.assertEquals(matches[0].x, 100)129 self.assertEquals(matches[0].x, 100)
128 self.assertEquals(matches[0].y, 200)130 self.assertEquals(matches[0].y, 200)
129 self.assertEquals(matches[1].x, 198)131 self.assertEquals(matches[1].x, 0)
130 self.assertEquals(matches[1].y, 100)132 self.assertEquals(matches[1].y, 100)
131133
132 def test_find_with_array_image(self):134 def test_find_with_array_image(self):
@@ -136,10 +138,7 @@
136 filename = self.green_square.filename138 filename = self.green_square.filename
137 self.green_square.filename = None139 self.green_square.filename = None
138140
139 # Use gtk to transform the image into a numpy array, and set it141 self.green_square.array = SimpleCV.Image(filename)
140 # back into the image.
141 pixbuf = gtk.image_new_from_file(filename).get_pixbuf()
142 self.green_square.array = pixbuf.get_pixels_array()
143142
144 # Try to match normally.143 # Try to match normally.
145 match = self.finder.find(self.screen_image, self.green_square)144 match = self.finder.find(self.screen_image, self.green_square)
146145
=== 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 @@
20import threading20import threading
21import time21import time
2222
23import gtk23from gi.repository import Gtk, Gdk
2424
25from xpresser import Xpresser, ImageNotFound25from xpresser import Xpresser, ImageNotFound
26from xpresser.image import Image26from xpresser.image import Image
@@ -49,7 +49,7 @@
49 self.assertEquals(image.name, "red-circle")49 self.assertEquals(image.name, "red-circle")
5050
51 def test_type(self):51 def test_type(self):
52 entry = gtk.Entry()52 entry = Gtk.Entry()
53 window = self.create_window(entry)53 window = self.create_window(entry)
54 try:54 try:
55 window.present()55 window.present()
@@ -72,11 +72,11 @@
72 self.button_rclicked = False72 self.button_rclicked = False
73 self.button_hovered = False73 self.button_hovered = False
74 self.button_dclicked = False74 self.button_dclicked = False
75 75
76 def clicked(widget, event):76 def clicked(widget, event):
77 if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:77 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
78 self.button_dclicked = True78 self.button_dclicked = True
79 elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:79 elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
80 self.button_clicked = True80 self.button_clicked = True
81 elif event.button == 3:81 elif event.button == 3:
82 self.button_rclicked = True82 self.button_rclicked = True
@@ -93,8 +93,9 @@
93 self.window.destroy()93 self.window.destroy()
9494
95 def get_button_center(self):95 def get_button_center(self):
96 button_x, button_y = self.button.window.get_position()96 button_x, button_y = self.button.get_window().get_position()
97 button_width, button_height = self.button.window.get_size()97 button_width = self.button.get_window().get_width()
98 button_height = self.button.get_window().get_height()
98 return (button_x + button_width//2, button_y + button_height//2)99 return (button_x + button_width//2, button_y + button_height//2)
99100
100 def test_find_image_name(self):101 def test_find_image_name(self):
@@ -123,6 +124,8 @@
123124
124 def test_find_failed(self):125 def test_find_failed(self):
125 started = time.time()126 started = time.time()
127 image = self.xp.get_image("blue-square")
128 image.similarity = 1.0
126 self.assertRaises(ImageNotFound,129 self.assertRaises(ImageNotFound,
127 self.xp.find, "blue-square", timeout=SLEEP_DELAY)130 self.xp.find, "blue-square", timeout=SLEEP_DELAY)
128 self.assertTrue(time.time() - started > SLEEP_DELAY)131 self.assertTrue(time.time() - started > SLEEP_DELAY)
@@ -142,6 +145,8 @@
142145
143 def test_wait_failed(self):146 def test_wait_failed(self):
144 started = time.time()147 started = time.time()
148 image = self.xp.get_image("blue-square")
149 image.similarity = 1.0
145 self.assertRaises(ImageNotFound,150 self.assertRaises(ImageNotFound,
146 self.xp.wait, "blue-square", timeout=SLEEP_DELAY)151 self.xp.wait, "blue-square", timeout=SLEEP_DELAY)
147 self.assertTrue(time.time() - started > SLEEP_DELAY)152 self.assertTrue(time.time() - started > SLEEP_DELAY)
148153
=== 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 @@
19#19#
20import time20import time
2121
22import gtk22from gi.repository import Gtk, Gdk, GdkPixbuf
2323
24from xpresser import xutils24from xpresser import xutils
25from xpresser.image import Image25from xpresser.image import Image
@@ -39,17 +39,17 @@
39 # actually trying to click on it? :-( If we just run until there39 # actually trying to click on it? :-( If we just run until there
40 # are no more events, and without sleep, the button will simply40 # are no more events, and without sleep, the button will simply
41 # return (0, 0) as its position.41 # return (0, 0) as its position.
42 while gtk.events_pending():42 while Gtk.events_pending():
43 gtk.main_iteration()43 Gtk.main_iteration()
44 time.sleep(0.1) # Why oh why? :-(44 time.sleep(0.1) # Why oh why? :-(
45 while gtk.events_pending():45 while Gtk.events_pending():
46 gtk.main_iteration()46 Gtk.main_iteration()
47 time.sleep(0.1)47 time.sleep(0.1)
48 while gtk.events_pending():48 while Gtk.events_pending():
49 gtk.main_iteration()49 Gtk.main_iteration()
5050
51 def create_window(self, child):51 def create_window(self, child):
52 window = gtk.Window(gtk.WINDOW_TOPLEVEL)52 window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
53 window.connect("delete_event", lambda widget, event: False)53 window.connect("delete_event", lambda widget, event: False)
54 window.add(child)54 window.add(child)
55 child.show()55 child.show()
@@ -57,21 +57,24 @@
57 return window57 return window
5858
59 def create_button_window(self, image_path=None):59 def create_button_window(self, image_path=None):
60 button = gtk.Button()60 button = Gtk.Button()
61 if image_path is None:61 if image_path is None:
62 image_path = get_image_path("red-square.png")62 image_path = get_image_path("red-square.png")
63 button.set_image(gtk.image_new_from_file(image_path))63 pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
64 image = Gtk.Image.new_from_pixbuf(pixbuf)
65 button.set_image(image)
64 return self.create_window(button)66 return self.create_window(button)
6567
66 def create_image_window(self, image_path):68 def create_image_window(self, image_path):
67 image = gtk.image_new_from_file(image_path)69 pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
70 image = Gtk.Image.new_from_pixbuf(pixbuf)
68 return self.create_window(image)71 return self.create_window(image)
6972
7073
71class XUtilsTest(XUtilsTestBase):74class XUtilsTest(XUtilsTestBase):
7275
73 def test_type(self):76 def test_type(self):
74 entry = gtk.Entry()77 entry = Gtk.Entry()
75 window = self.create_window(entry)78 window = self.create_window(entry)
76 try:79 try:
77 window.present()80 window.present()
@@ -95,51 +98,36 @@
9598
96 self.flush_gtk()99 self.flush_gtk()
97100
98 resolution = gtk.gdk.get_default_root_window().get_size()101 width = Gdk.get_default_root_window().get_width()
99102 height = Gdk.get_default_root_window().get_height()
100 window_x, window_y = window.get_child().window.get_position()103
101 window_width, window_height = window.get_child().window.get_size()104 window_x, window_y = window.get_child().get_window().get_position()
102105
106 time.sleep(2)
103 big_screenshot = xutils.take_screenshot()107 big_screenshot = xutils.take_screenshot()
104 small_screenshot = xutils.take_screenshot(window_x, window_y,
105 window_width, window_height)
106108
107 window.destroy()109 window.destroy()
108 self.flush_gtk()110 self.flush_gtk()
109111
110 # Check the basic attributes set112 # Check the basic attributes set
111 self.assertEquals(big_screenshot.name, "screenshot")113 self.assertEquals(big_screenshot.name, "screenshot")
112 self.assertEquals(big_screenshot.width, resolution[0])114 self.assertEquals(big_screenshot.width, width)
113 self.assertEquals(big_screenshot.height, resolution[1])115 self.assertEquals(big_screenshot.height, height)
114
115 self.assertEquals(small_screenshot.name, "screenshot")
116 self.assertEquals(small_screenshot.width, window_width)
117 self.assertEquals(small_screenshot.height, window_height)
118116
119 # Now verify the actual images taken.117 # Now verify the actual images taken.
120 finder = OpenCVFinder()118 finder = OpenCVFinder()
121119
122 big_match = finder.find(big_screenshot, red_square)120 big_match = finder.find(big_screenshot, red_square)
123 small_match = finder.find(small_screenshot, red_square)
124121
125 self.assertEquals(big_match.image, red_square)122 self.assertEquals(big_match.image, red_square)
126 self.assertTrue(big_match.similarity > 0.95, big_match.similarity)123 self.assertTrue(big_match.similarity > 0.95, big_match.similarity)
127124
128 self.assertEquals(small_match.image, red_square)
129 self.assertTrue(small_match.similarity > 0.95, small_match.similarity)
130
131 # The match we found in the big screenshot should be in the same125 # The match we found in the big screenshot should be in the same
132 # position as the window we created. Note that this may fail if126 # position as the window we created. Note that this may fail if
133 # you have the image opened elsewhere. ;-)127 # you have the image opened elsewhere. ;-)
134 self.assertEquals(big_match.x, window_x)128 self.assertEquals(big_match.x, window_x)
135 self.assertEquals(big_match.y, window_y)129 self.assertEquals(big_match.y, window_y)
136130
137 # With the small match, it should be in the origin, since the
138 # screenshot was taken on the precise area.
139 self.assertEquals(small_match.x, 0)
140 self.assertEquals(small_match.y, 0)
141
142
143131
144class XUtilsButtonTest(XUtilsTestBase):132class XUtilsButtonTest(XUtilsTestBase):
145133
@@ -153,13 +141,13 @@
153 self.button_dclicked = False141 self.button_dclicked = False
154142
155 def clicked(widget, event):143 def clicked(widget, event):
156 if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:144 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
157 self.button_dclicked = True145 self.button_dclicked = True
158 elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:146 elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
159 self.button_clicked = True147 self.button_clicked = True
160 elif event.button == 3:148 elif event.button == 3:
161 self.button_rclicked = True149 self.button_rclicked = True
162 150
163 def entered(widget):151 def entered(widget):
164 self.button_hovered = True152 self.button_hovered = True
165153
@@ -172,8 +160,9 @@
172 self.window.destroy()160 self.window.destroy()
173161
174 def get_button_center(self):162 def get_button_center(self):
175 button_x, button_y = self.button.window.get_position()163 button_x, button_y = self.button.get_window().get_position()
176 button_width, button_height = self.button.window.get_size()164 button_width = self.button.get_window().get_width()
165 button_height = self.button.get_window().get_height()
177 return (button_x + button_width//2, button_y + button_height//2)166 return (button_x + button_width//2, button_y + button_height//2)
178 self.window.destroy()167 self.window.destroy()
179168
180169
=== 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 @@
59 """Click on the position specified by the provided arguments.59 """Click on the position specified by the provided arguments.
6060
61 The following examples show valid ways of specifying the position:61 The following examples show valid ways of specifying the position:
62 62
63 xp.click("image-name")63 xp.click("image-name")
64 xp.click(image_match)64 xp.click(image_match)
65 xp.click(x, y)65 xp.click(x, y)
@@ -70,7 +70,7 @@
70 """Right-click on the position specified by the provided arguments.70 """Right-click on the position specified by the provided arguments.
7171
72 The following examples show valid ways of specifying the position:72 The following examples show valid ways of specifying the position:
73 73
74 xp.right_click("image-name")74 xp.right_click("image-name")
75 xp.right_click(image_match)75 xp.right_click(image_match)
76 xp.right_click(x, y)76 xp.right_click(x, y)
@@ -80,18 +80,18 @@
80 def double_click(self, *args):80 def double_click(self, *args):
81 '''Double clicks over the position specified by arguments81 '''Double clicks over the position specified by arguments
8282
83 The following examples show valid ways of specifying te position:83 The following examples show valid ways of specifying the position:
84 xp.double_click("image-name")84 xp.double_click("image-name")
85 xp.double_click(image_match)85 xp.double_click(image_match)
86 xp.double_click(x, y)86 xp.double_click(x, y)
87 '''87 '''
88 xutils.double_click(*self._compute_focus_point(args))88 xutils.double_click(*self._compute_focus_point(args))
89 89
90 def hover(self, *args):90 def hover(self, *args):
91 """Hover over the position specified by the provided arguments.91 """Hover over the position specified by the provided arguments.
9292
93 The following examples show valid ways of specifying the position:93 The following examples show valid ways of specifying the position:
94 94
95 xp.hover("image-name")95 xp.hover("image-name")
96 xp.hover(image_match)96 xp.hover(image_match)
97 xp.hover(x, y)97 xp.hover(x, y)
9898
=== 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 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#19#
20import pyatspi
21import SimpleCV
20from xpresser.image import Image22from xpresser.image import Image
2123from tempfile import NamedTemporaryFile
22import pyatspi24from gi.repository import Gdk
23import gtk
24
25import warnings
26
27# pygtk is using a deprecated method from numpy in get_pixels_array().
28warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*")
2925
3026
31def click(x, y):27def click(x, y):
@@ -42,21 +38,14 @@
4238
43def type(string):39def type(string):
44 for char in string:40 for char in string:
45 keyval = gtk.gdk.unicode_to_keyval(ord(char))41 keyval = Gdk.unicode_to_keyval(ord(char))
46 pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) 42 pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM)
4743
4844def take_screenshot():
49def take_screenshot(x=0, y=0, width=None, height=None):45 window = Gdk.get_default_root_window()
50 window = gtk.gdk.get_default_root_window()46 surface = Gdk.cairo_create(window).get_target()
51 if not (width and height):47 with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f:
52 size = window.get_size()48 surface.write_to_png(f.name)
53 if not width:49 opencv_image = SimpleCV.Image(f.name)
54 width = size[0]50 return Image("screenshot", array=opencv_image,
55 if not height:51 width=opencv_image.width, height=opencv_image.height)
56 height = size[1]
57 pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
58 pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(),
59 x, y, 0, 0, width, height)
60 array = pixbuf.get_pixels_array()
61 return Image("screenshot", array=array,
62 width=array.shape[1], height=array.shape[0])

Subscribers

People subscribed via source and target branches

to all changes: