Merge lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser
- gtk3-port-with-SimpleCV
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Xpresser | Pending | ||
Review via email: mp+113582@code.launchpad.net |
Commit message
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
1 | === added directory 'debian' |
2 | === added file 'debian/changelog' |
3 | --- debian/changelog 1970-01-01 00:00:00 +0000 |
4 | +++ debian/changelog 2012-07-05 13:34:35 +0000 |
5 | @@ -0,0 +1,71 @@ |
6 | +xpresser (1.0-1ubuntu11) oneiric; urgency=low |
7 | + |
8 | + * Added special keypresses to xutils.py |
9 | + |
10 | + -- Chris Gagnon <chris.gagnon@canonical.com> Wed, 07 Mar 2012 20:27:57 -0500 |
11 | + |
12 | +xpresser (1.0-1ubuntu10) oneiric; urgency=low |
13 | + |
14 | + * Added double click functionality |
15 | + |
16 | + -- Chris Wayne <chris.wayne@canonical.com> Fri, 09 Dec 2011 12:57:52 -0500 |
17 | + |
18 | +xpresser (1.0-1ubuntu9) oneiric; urgency=low |
19 | + |
20 | + * pyatspi2 fixes |
21 | + |
22 | + -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 13:37:45 -0500 |
23 | + |
24 | +xpresser (1.0-1ubuntu8) oneiric; urgency=low |
25 | + |
26 | + * fixed broken deps |
27 | + |
28 | + -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 12:03:40 -0500 |
29 | + |
30 | +xpresser (1.0-1ubuntu7) oneiric; urgency=low |
31 | + |
32 | + * Having xpresser use pyatspi2 instead of pyatspi to work in oneiric |
33 | + |
34 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 29 Nov 2011 10:05:10 -0500 |
35 | + |
36 | +xpresser (1.0-1ubuntu6) natty; urgency=low |
37 | + |
38 | + * building for natty for pes qa ppa |
39 | + |
40 | + -- Chris Wayne <chris.wayne@canonical.com> Fri, 11 Nov 2011 16:29:17 -0500 |
41 | + |
42 | +xpresser (1.0-1ubuntu5) natty; urgency=low |
43 | + |
44 | + * Version bump |
45 | + |
46 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:21:07 -0400 |
47 | + |
48 | +xpresser (1.0-1ubuntu4) natty; urgency=low |
49 | + |
50 | + * building for natty |
51 | + |
52 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:10:58 -0400 |
53 | + |
54 | +xpresser (1.0-1ubuntu3) oneiric; urgency=low |
55 | + |
56 | + * building for oem services qa ppa |
57 | + |
58 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:35:14 -0400 |
59 | + |
60 | +xpresser (1.0-1ubuntu2) oneiric; urgency=low |
61 | + |
62 | + * Try 2 |
63 | + |
64 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:19:09 -0400 |
65 | + |
66 | +xpresser (1.0-1ubuntu1) oneiric; urgency=low |
67 | + |
68 | + * Trying to debianize |
69 | + |
70 | + -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:14:53 -0400 |
71 | + |
72 | +xpresser (1.0-1) unstable; urgency=low |
73 | + |
74 | + * source package automatically created by stdeb 0.6.0+git |
75 | + |
76 | + -- Gustavo Niemeyer <gustavo.niemeyer@canonical.com> Tue, 25 Oct 2011 15:12:21 -0400 |
77 | |
78 | === added file 'debian/compat' |
79 | --- debian/compat 1970-01-01 00:00:00 +0000 |
80 | +++ debian/compat 2012-07-05 13:34:35 +0000 |
81 | @@ -0,0 +1,1 @@ |
82 | +7 |
83 | |
84 | === added file 'debian/control' |
85 | --- debian/control 1970-01-01 00:00:00 +0000 |
86 | +++ debian/control 2012-07-05 13:34:35 +0000 |
87 | @@ -0,0 +1,12 @@ |
88 | +Source: xpresser |
89 | +Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com> |
90 | +Section: python |
91 | +Priority: optional |
92 | +Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7) |
93 | +Standards-Version: 3.9.1 |
94 | + |
95 | +Package: python-xpresser |
96 | +Architecture: all |
97 | +Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-pyatspi2, python-numpy |
98 | +Description: Python library to script Graphic User Interfaces. |
99 | + |
100 | |
101 | === added file 'debian/files' |
102 | --- debian/files 1970-01-01 00:00:00 +0000 |
103 | +++ debian/files 2012-07-05 13:34:35 +0000 |
104 | @@ -0,0 +1,1 @@ |
105 | +python-xpresser_1.0-1ubuntu13_all.deb python optional |
106 | |
107 | === added directory 'debian/python-xpresser' |
108 | === added file 'debian/python-xpresser.debhelper.log' |
109 | --- debian/python-xpresser.debhelper.log 1970-01-01 00:00:00 +0000 |
110 | +++ debian/python-xpresser.debhelper.log 2012-07-05 13:34:35 +0000 |
111 | @@ -0,0 +1,46 @@ |
112 | +dh_auto_configure |
113 | +dh_auto_build |
114 | +dh_auto_test |
115 | +dh_prep |
116 | +dh_installdirs |
117 | +dh_auto_install |
118 | +dh_install |
119 | +dh_installdocs |
120 | +dh_installchangelogs |
121 | +dh_installexamples |
122 | +dh_installman |
123 | +dh_installcatalogs |
124 | +dh_installcron |
125 | +dh_installdebconf |
126 | +dh_installemacsen |
127 | +dh_installifupdown |
128 | +dh_installinfo |
129 | +dh_installinit |
130 | +dh_installmenu |
131 | +dh_installmime |
132 | +dh_installmodules |
133 | +dh_installlogcheck |
134 | +dh_installlogrotate |
135 | +dh_installpam |
136 | +dh_installppp |
137 | +dh_installudev |
138 | +dh_installwm |
139 | +dh_installxfonts |
140 | +dh_installgsettings |
141 | +dh_bugfiles |
142 | +dh_ucf |
143 | +dh_lintian |
144 | +dh_gconf |
145 | +dh_icons |
146 | +dh_perl |
147 | +dh_usrlocal |
148 | +dh_link |
149 | +dh_compress |
150 | +dh_fixperms |
151 | +dh_strip |
152 | +dh_makeshlibs |
153 | +dh_shlibdeps |
154 | +dh_installdeb |
155 | +dh_gencontrol |
156 | +dh_md5sums |
157 | +dh_builddeb |
158 | |
159 | === added file 'debian/python-xpresser.postinst.debhelper' |
160 | --- debian/python-xpresser.postinst.debhelper 1970-01-01 00:00:00 +0000 |
161 | +++ debian/python-xpresser.postinst.debhelper 2012-07-05 13:34:35 +0000 |
162 | @@ -0,0 +1,7 @@ |
163 | + |
164 | +# Automatically added by dh_python2: |
165 | +if which pycompile >/dev/null 2>&1; then |
166 | + pycompile -p python-xpresser |
167 | +fi |
168 | + |
169 | +# End automatically added section |
170 | |
171 | === added file 'debian/python-xpresser.prerm.debhelper' |
172 | --- debian/python-xpresser.prerm.debhelper 1970-01-01 00:00:00 +0000 |
173 | +++ debian/python-xpresser.prerm.debhelper 2012-07-05 13:34:35 +0000 |
174 | @@ -0,0 +1,12 @@ |
175 | + |
176 | +# Automatically added by dh_python2: |
177 | +if which pyclean >/dev/null 2>&1; then |
178 | + pyclean -p python-xpresser |
179 | +else |
180 | + dpkg -L python-xpresser | grep \.py$ | while read file |
181 | + do |
182 | + rm -f "${file}"[co] >/dev/null |
183 | + done |
184 | +fi |
185 | + |
186 | +# End automatically added section |
187 | |
188 | === added file 'debian/python-xpresser.substvars' |
189 | --- debian/python-xpresser.substvars 1970-01-01 00:00:00 +0000 |
190 | +++ debian/python-xpresser.substvars 2012-07-05 13:34:35 +0000 |
191 | @@ -0,0 +1,4 @@ |
192 | +python:Versions=2.6, 2.7 |
193 | +python:Provides=python2.6-xpresser, python2.7-xpresser |
194 | +python:Depends=python2.7 | python2.6, python (>= 2.6), python (<< 2.8), python (>= 2.7.1-0ubuntu2) |
195 | +misc:Depends= |
196 | |
197 | === added directory 'debian/python-xpresser/DEBIAN' |
198 | === added file 'debian/python-xpresser/DEBIAN/control' |
199 | --- debian/python-xpresser/DEBIAN/control 1970-01-01 00:00:00 +0000 |
200 | +++ debian/python-xpresser/DEBIAN/control 2012-07-05 13:34:35 +0000 |
201 | @@ -0,0 +1,10 @@ |
202 | +Package: python-xpresser |
203 | +Source: xpresser |
204 | +Version: 1.0-1ubuntu13 |
205 | +Architecture: all |
206 | +Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com> |
207 | +Installed-Size: 124 |
208 | +Depends: python2.7 | python2.6, python (>= 2.7.1-0ubuntu2), python (<< 2.8), python-opencv, python-pyatspi2, python-numpy |
209 | +Section: python |
210 | +Priority: optional |
211 | +Description: Python library to script Graphic User Interfaces. |
212 | |
213 | === added file 'debian/python-xpresser/DEBIAN/md5sums' |
214 | --- debian/python-xpresser/DEBIAN/md5sums 1970-01-01 00:00:00 +0000 |
215 | +++ debian/python-xpresser/DEBIAN/md5sums 2012-07-05 13:34:35 +0000 |
216 | @@ -0,0 +1,10 @@ |
217 | +bd68a0923da369be55405c3f89b177c6 usr/share/doc/python-xpresser/changelog.Debian.gz |
218 | +aeb2a0ddf75f75b58a90aefd7055836e usr/share/pyshared/Xpresser-1.0.egg-info |
219 | +e6ca66d62183fa913f0164fbe406d3dd usr/share/pyshared/xpresser/__init__.py |
220 | +24d5a84a02bdcdc08a4321e4a10d0ded usr/share/pyshared/xpresser/errors.py |
221 | +4feb915dd3b78b90df3c1506287d98fc usr/share/pyshared/xpresser/image.py |
222 | +8508962040806a29a7f961993d7021a9 usr/share/pyshared/xpresser/imagedir.py |
223 | +a709a84956c69b7b869b8223b80f58f2 usr/share/pyshared/xpresser/imagematch.py |
224 | +3ec633b271f373011520336ac9a1737a usr/share/pyshared/xpresser/opencvfinder.py |
225 | +8820163100e7cf105521aa0732b50c54 usr/share/pyshared/xpresser/xp.py |
226 | +3b5279525e533b84d50fe9afeef1b502 usr/share/pyshared/xpresser/xutils.py |
227 | |
228 | === added file 'debian/python-xpresser/DEBIAN/postinst' |
229 | --- debian/python-xpresser/DEBIAN/postinst 1970-01-01 00:00:00 +0000 |
230 | +++ debian/python-xpresser/DEBIAN/postinst 2012-07-05 13:34:35 +0000 |
231 | @@ -0,0 +1,9 @@ |
232 | +#!/bin/sh |
233 | +set -e |
234 | + |
235 | +# Automatically added by dh_python2: |
236 | +if which pycompile >/dev/null 2>&1; then |
237 | + pycompile -p python-xpresser |
238 | +fi |
239 | + |
240 | +# End automatically added section |
241 | |
242 | === added file 'debian/python-xpresser/DEBIAN/prerm' |
243 | --- debian/python-xpresser/DEBIAN/prerm 1970-01-01 00:00:00 +0000 |
244 | +++ debian/python-xpresser/DEBIAN/prerm 2012-07-05 13:34:35 +0000 |
245 | @@ -0,0 +1,14 @@ |
246 | +#!/bin/sh |
247 | +set -e |
248 | + |
249 | +# Automatically added by dh_python2: |
250 | +if which pyclean >/dev/null 2>&1; then |
251 | + pyclean -p python-xpresser |
252 | +else |
253 | + dpkg -L python-xpresser | grep \.py$ | while read file |
254 | + do |
255 | + rm -f "${file}"[co] >/dev/null |
256 | + done |
257 | +fi |
258 | + |
259 | +# End automatically added section |
260 | |
261 | === added directory 'debian/python-xpresser/usr' |
262 | === added directory 'debian/python-xpresser/usr/lib' |
263 | === added directory 'debian/python-xpresser/usr/lib/python2.6' |
264 | === added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages' |
265 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/Xpresser-1.0.egg-info' |
266 | === target is u'../../../share/pyshared/Xpresser-1.0.egg-info' |
267 | === added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser' |
268 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/__init__.py' |
269 | === target is u'../../../../share/pyshared/xpresser/__init__.py' |
270 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/errors.py' |
271 | === target is u'../../../../share/pyshared/xpresser/errors.py' |
272 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/image.py' |
273 | === target is u'../../../../share/pyshared/xpresser/image.py' |
274 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagedir.py' |
275 | === target is u'../../../../share/pyshared/xpresser/imagedir.py' |
276 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagematch.py' |
277 | === target is u'../../../../share/pyshared/xpresser/imagematch.py' |
278 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/opencvfinder.py' |
279 | === target is u'../../../../share/pyshared/xpresser/opencvfinder.py' |
280 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xp.py' |
281 | === target is u'../../../../share/pyshared/xpresser/xp.py' |
282 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xutils.py' |
283 | === target is u'../../../../share/pyshared/xpresser/xutils.py' |
284 | === added directory 'debian/python-xpresser/usr/lib/python2.7' |
285 | === added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages' |
286 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/Xpresser-1.0.egg-info' |
287 | === target is u'../../../share/pyshared/Xpresser-1.0.egg-info' |
288 | === added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser' |
289 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/__init__.py' |
290 | === target is u'../../../../share/pyshared/xpresser/__init__.py' |
291 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/errors.py' |
292 | === target is u'../../../../share/pyshared/xpresser/errors.py' |
293 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/image.py' |
294 | === target is u'../../../../share/pyshared/xpresser/image.py' |
295 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagedir.py' |
296 | === target is u'../../../../share/pyshared/xpresser/imagedir.py' |
297 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagematch.py' |
298 | === target is u'../../../../share/pyshared/xpresser/imagematch.py' |
299 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/opencvfinder.py' |
300 | === target is u'../../../../share/pyshared/xpresser/opencvfinder.py' |
301 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xp.py' |
302 | === target is u'../../../../share/pyshared/xpresser/xp.py' |
303 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xutils.py' |
304 | === target is u'../../../../share/pyshared/xpresser/xutils.py' |
305 | === added directory 'debian/python-xpresser/usr/share' |
306 | === added directory 'debian/python-xpresser/usr/share/doc' |
307 | === added directory 'debian/python-xpresser/usr/share/doc/python-xpresser' |
308 | === added file 'debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz' |
309 | 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 |
310 | === added directory 'debian/python-xpresser/usr/share/pyshared' |
311 | === added file 'debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info' |
312 | --- debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 1970-01-01 00:00:00 +0000 |
313 | +++ debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 2012-07-05 13:34:35 +0000 |
314 | @@ -0,0 +1,10 @@ |
315 | +Metadata-Version: 1.0 |
316 | +Name: Xpresser |
317 | +Version: 1.0 |
318 | +Summary: Python library to script Graphic User Interfaces. |
319 | +Home-page: https://edge.launchpad.net/xpresser |
320 | +Author: Gustavo Niemeyer |
321 | +Author-email: gustavo.niemeyer@canonical.com |
322 | +License: UNKNOWN |
323 | +Description: UNKNOWN |
324 | +Platform: UNKNOWN |
325 | |
326 | === added directory 'debian/python-xpresser/usr/share/pyshared/xpresser' |
327 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py' |
328 | --- debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 1970-01-01 00:00:00 +0000 |
329 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 2012-07-05 13:34:35 +0000 |
330 | @@ -0,0 +1,23 @@ |
331 | +# |
332 | +# Copyright (c) 2010 Canonical |
333 | +# |
334 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
335 | +# |
336 | +# This file is part of the Xpresser GUI automation library. |
337 | +# |
338 | +# Xpresser is free software; you can redistribute it and/or modify |
339 | +# it under the terms of the GNU Lesser General Public License version 3, |
340 | +# as published by the Free Software Foundation. |
341 | +# |
342 | +# Xpresser is distributed in the hope that it will be useful, |
343 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
344 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
345 | +# GNU Lesser General Public License for more details. |
346 | +# |
347 | +# You should have received a copy of the GNU Lesser General Public License |
348 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
349 | +# |
350 | +import pygtk |
351 | +pygtk.require("2.0") |
352 | + |
353 | +from xpresser.xp import Xpresser, ImageNotFound |
354 | |
355 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/errors.py' |
356 | --- debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 1970-01-01 00:00:00 +0000 |
357 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 2012-07-05 13:34:35 +0000 |
358 | @@ -0,0 +1,23 @@ |
359 | +# |
360 | +# Copyright (c) 2010 Canonical |
361 | +# |
362 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
363 | +# |
364 | +# This file is part of the Xpresser GUI automation library. |
365 | +# |
366 | +# Xpresser is free software; you can redistribute it and/or modify |
367 | +# it under the terms of the GNU Lesser General Public License version 3, |
368 | +# as published by the Free Software Foundation. |
369 | +# |
370 | +# Xpresser is distributed in the hope that it will be useful, |
371 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
372 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
373 | +# GNU Lesser General Public License for more details. |
374 | +# |
375 | +# You should have received a copy of the GNU Lesser General Public License |
376 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
377 | +# |
378 | + |
379 | +class XpresserError(Exception): |
380 | + """Base class for all Xpresser exceptions.""" |
381 | + |
382 | |
383 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/image.py' |
384 | --- debian/python-xpresser/usr/share/pyshared/xpresser/image.py 1970-01-01 00:00:00 +0000 |
385 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/image.py 2012-07-05 13:34:35 +0000 |
386 | @@ -0,0 +1,76 @@ |
387 | +# |
388 | +# Copyright (c) 2010 Canonical |
389 | +# |
390 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
391 | +# |
392 | +# This file is part of the Xpresser GUI automation library. |
393 | +# |
394 | +# Xpresser is free software; you can redistribute it and/or modify |
395 | +# it under the terms of the GNU Lesser General Public License version 3, |
396 | +# as published by the Free Software Foundation. |
397 | +# |
398 | +# Xpresser is distributed in the hope that it will be useful, |
399 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
400 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
401 | +# GNU Lesser General Public License for more details. |
402 | +# |
403 | +# You should have received a copy of the GNU Lesser General Public License |
404 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
405 | +# |
406 | + |
407 | + |
408 | +DEFAULT_SIMILARITY = 0.98 |
409 | + |
410 | + |
411 | +class Image(object): |
412 | + """An image. :-) |
413 | + |
414 | + @ivar name: The human-oriented name of this image. May be None. |
415 | + |
416 | + @ivar similarity: The similarity tolerance to be used when searching |
417 | + for this image. |
418 | + |
419 | + Varies between 0.0 and 1.0, where 1.0 is a perfect match. Defaults |
420 | + to the value of DEFAULT_SIMILARITY when not specified in the image |
421 | + data. |
422 | + |
423 | + @ivar focus_delta: (dx, dy) pair added to the center position to |
424 | + find where to click. |
425 | + |
426 | + For instance, if the *center* of the image is found at 200, 300 and |
427 | + the focus_point is (10, -20) the click will actually happen at the |
428 | + screen position (210, 280). |
429 | + |
430 | + When not specified, (0, 0) is assumed, which means click in the |
431 | + center of the image itself. |
432 | + |
433 | + @ivar width: The width of the image. |
434 | + |
435 | + @ivar height: The height of the image. |
436 | + |
437 | + @ivar filename: Filename of the image. |
438 | + |
439 | + @ivar array: Numpy array with three dimensions (rows, columns, RGB). |
440 | + |
441 | + @ivar cache: Generic storage for data associated with this image, used |
442 | + by the image finder, for instance. |
443 | + """ |
444 | + |
445 | + def __init__(self, name=None, similarity=None, focus_delta=None, |
446 | + width=None, height=None, filename=None, array=None): |
447 | + if similarity is None: |
448 | + similarity = DEFAULT_SIMILARITY |
449 | + if focus_delta is None: |
450 | + focus_delta = (0, 0) |
451 | + |
452 | + if not (0 < similarity < 1): |
453 | + raise ValueError("Similarity out of range: %.2f" % similarity) |
454 | + |
455 | + self.name = name |
456 | + self.similarity = similarity |
457 | + self.focus_delta = focus_delta |
458 | + self.width = width |
459 | + self.height = height |
460 | + self.filename = filename |
461 | + self.array = array |
462 | + self.cache = {} |
463 | |
464 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py' |
465 | --- debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 1970-01-01 00:00:00 +0000 |
466 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 2012-07-05 13:34:35 +0000 |
467 | @@ -0,0 +1,111 @@ |
468 | +# |
469 | +# Copyright (c) 2010 Canonical |
470 | +# |
471 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
472 | +# |
473 | +# This file is part of the Xpresser GUI automation library. |
474 | +# |
475 | +# Xpresser is free software; you can redistribute it and/or modify |
476 | +# it under the terms of the GNU Lesser General Public License version 3, |
477 | +# as published by the Free Software Foundation. |
478 | +# |
479 | +# Xpresser is distributed in the hope that it will be useful, |
480 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
481 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
482 | +# GNU Lesser General Public License for more details. |
483 | +# |
484 | +# You should have received a copy of the GNU Lesser General Public License |
485 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
486 | +# |
487 | +import mimetypes |
488 | +import ConfigParser |
489 | +import os |
490 | +import re |
491 | + |
492 | +from xpresser.errors import XpresserError |
493 | +from xpresser.image import Image |
494 | + |
495 | + |
496 | +CLICK_POSITION_RE = re.compile(r"^\s*(?P<x>[-+][0-9]+)\s+(?P<y>[-+][0-9]+)\s*$") |
497 | + |
498 | + |
499 | +class ImageDirError(XpresserError): |
500 | + """Error related to the image directory.""" |
501 | + |
502 | + |
503 | +class ImageDir(object): |
504 | + """Represents a directory with data about images. |
505 | + |
506 | + This class doesn't know about any details regarding the images |
507 | + themselves, besides the existence of the file in which they reside. |
508 | + It will give access to generic ImageData objects containing the |
509 | + details about these images. It's up to an ImageLoader to make sense |
510 | + of the actual image data contained in the image files. |
511 | + """ |
512 | + |
513 | + def __init__(self): |
514 | + self._images = {} |
515 | + |
516 | + def get(self, image_name): |
517 | + return self._images.get(image_name) |
518 | + |
519 | + def load(self, dirname): |
520 | + """Load image information from C{dirname}. |
521 | + |
522 | + @param dirname: Path of directory containing xpresser.ini. |
523 | + """ |
524 | + loaded_filenames = set() |
525 | + ini_filename = os.path.join(dirname, "xpresser.ini") |
526 | + if os.path.exists(ini_filename): |
527 | + config = ConfigParser.ConfigParser() |
528 | + config.read(ini_filename) |
529 | + for section_name in config.sections(): |
530 | + if section_name.startswith("image "): |
531 | + image_name = section_name.split(None, 1)[1] |
532 | + try: |
533 | + image_filename = config.get(section_name, "filename") |
534 | + except ConfigParser.NoOptionError: |
535 | + raise ImageDirError("Image %s missing filename option" |
536 | + % image_name) |
537 | + image_filename = os.path.join(dirname, image_filename) |
538 | + if not os.path.exists(image_filename): |
539 | + raise ImageDirError("Image %s file not found: %s" % |
540 | + (image_name, image_filename)) |
541 | + try: |
542 | + image_similarity = config.getfloat(section_name, |
543 | + "similarity") |
544 | + except ConfigParser.NoOptionError: |
545 | + image_similarity = None |
546 | + except ValueError: |
547 | + value = config.get(section_name, "similarity") |
548 | + raise ImageDirError("Image %s has bad similarity: %s" |
549 | + % (image_name, value)) |
550 | + |
551 | + try: |
552 | + value = config.get(section_name, "focus_delta") |
553 | + match = CLICK_POSITION_RE.match(value) |
554 | + if not match: |
555 | + raise ImageDirError("Image %s has invalid click " |
556 | + "position: %s" % |
557 | + (image_name, value)) |
558 | + image_focus_delta = (int(match.group("x")), |
559 | + int(match.group("y"))) |
560 | + except ConfigParser.NoOptionError: |
561 | + image_focus_delta = None |
562 | + image = Image(name=image_name, |
563 | + filename=image_filename, |
564 | + similarity=image_similarity, |
565 | + focus_delta=image_focus_delta) |
566 | + self._images[image_name] = image |
567 | + loaded_filenames.add(image_filename) |
568 | + |
569 | + # Load any other images implicitly with the default arguments. |
570 | + for basename in os.listdir(dirname): |
571 | + filename = os.path.join(dirname, basename) |
572 | + if filename not in loaded_filenames: |
573 | + ftype, fencoding = mimetypes.guess_type(filename) |
574 | + if ftype and ftype.startswith("image/"): |
575 | + image_name = os.path.splitext(basename)[0] |
576 | + self._images[image_name] = Image(name=image_name, |
577 | + filename=filename) |
578 | + |
579 | |
580 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py' |
581 | --- debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 1970-01-01 00:00:00 +0000 |
582 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 2012-07-05 13:34:35 +0000 |
583 | @@ -0,0 +1,54 @@ |
584 | +# |
585 | +# Copyright (c) 2010 Canonical |
586 | +# |
587 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
588 | +# |
589 | +# This file is part of the Xpresser GUI automation library. |
590 | +# |
591 | +# Xpresser is free software; you can redistribute it and/or modify |
592 | +# it under the terms of the GNU Lesser General Public License version 3, |
593 | +# as published by the Free Software Foundation. |
594 | +# |
595 | +# Xpresser is distributed in the hope that it will be useful, |
596 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
597 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
598 | +# GNU Lesser General Public License for more details. |
599 | +# |
600 | +# You should have received a copy of the GNU Lesser General Public License |
601 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
602 | +# |
603 | +from xpresser.errors import XpresserError |
604 | + |
605 | + |
606 | +class ImageMatchError(XpresserError): |
607 | + """Error raised due to an ImageMatch related problem (really!).""" |
608 | + |
609 | + |
610 | +class ImageMatch(object): |
611 | + """An image found inside another image. |
612 | + |
613 | + @ivar image: The image found. |
614 | + @ivar x: Position in the X axis where the image was found. |
615 | + @ivar y: Position in the Y axis where the image was found. |
616 | + @ivar similarity: How similar to the original image the match was, |
617 | + where 1.0 == 100%. |
618 | + @ivar focus_point: The position in the screen which this image match |
619 | + represents. This is useful for clicks, hovering, etc. If no delta |
620 | + was specified in the image data itself, this will map to the center |
621 | + of the found image. |
622 | + """ |
623 | + |
624 | + def __init__(self, image, x, y, similarity): |
625 | + if image.height is None: |
626 | + raise ImageMatchError("Image.height was None when trying to " |
627 | + "create an ImageMatch with it.") |
628 | + if image.width is None: |
629 | + raise ImageMatchError("Image.width was None when trying to " |
630 | + "create an ImageMatch with it.") |
631 | + |
632 | + self.image = image |
633 | + self.x = x |
634 | + self.y = y |
635 | + self.similarity = similarity |
636 | + self.focus_point = (x + image.width//2 + image.focus_delta[0], |
637 | + y + image.height//2 + image.focus_delta[1]) |
638 | |
639 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py' |
640 | --- debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 1970-01-01 00:00:00 +0000 |
641 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000 |
642 | @@ -0,0 +1,142 @@ |
643 | +# |
644 | +# Copyright (c) 2010 Canonical |
645 | +# |
646 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
647 | +# |
648 | +# This file is part of the Xpresser GUI automation library. |
649 | +# |
650 | +# Xpresser is free software; you can redistribute it and/or modify |
651 | +# it under the terms of the GNU Lesser General Public License version 3, |
652 | +# as published by the Free Software Foundation. |
653 | +# |
654 | +# Xpresser is distributed in the hope that it will be useful, |
655 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
656 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
657 | +# GNU Lesser General Public License for more details. |
658 | +# |
659 | +# You should have received a copy of the GNU Lesser General Public License |
660 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
661 | +# |
662 | +import math |
663 | +import time |
664 | + |
665 | +import numpy |
666 | + |
667 | +import opencv |
668 | +import opencv.highgui |
669 | +import opencv.adaptors |
670 | + |
671 | +from xpresser.imagematch import ImageMatch |
672 | + |
673 | + |
674 | +FILTER_MARGIN = 25 # % |
675 | + |
676 | +DEBUG_PERFORMANCE = False |
677 | + |
678 | + |
679 | +class OpenCVFinder(object): |
680 | + |
681 | + def find(self, screen_image, area_image): |
682 | + matches = self._find(screen_image, area_image, best_match=True) |
683 | + if matches: |
684 | + matches.sort(key=lambda match: -match.similarity) |
685 | + return matches[0] |
686 | + return None |
687 | + |
688 | + def find_all(self, screen_image, area_image): |
689 | + return self._find(screen_image, area_image) |
690 | + |
691 | + def _load_image(self, image): |
692 | + if "opencv_image" not in image.cache: |
693 | + if image.filename is not None: |
694 | + opencv_image = opencv.highgui.cvLoadImage(image.filename) |
695 | + elif image.array is not None: |
696 | + # The adaptor function can't deal with the alpha channel. |
697 | + array = image.array[:,:,:3] |
698 | + opencv_image = opencv.adaptors.NumPy2Ipl(array) |
699 | + else: |
700 | + raise RuntimeError("Oops. Can't load image.") |
701 | + image.cache["opencv_image"] = opencv_image |
702 | + image.width = opencv_image.width |
703 | + image.height = opencv_image.height |
704 | + return image.cache["opencv_image"] |
705 | + |
706 | + def _find(self, screen_image, area_image, best_match=False): |
707 | + if DEBUG_PERFORMANCE: |
708 | + started = time.time() |
709 | + screen = self._load_image(screen_image) |
710 | + area = self._load_image(area_image) |
711 | + if DEBUG_PERFORMANCE: |
712 | + print "LOADING IMAGES: %.5fs" % (time.time()-started) |
713 | + |
714 | + result_width = screen.width - area.width + 1 |
715 | + result_height = screen.height - area.height + 1 |
716 | + result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height), |
717 | + opencv.IPL_DEPTH_32F, 1) |
718 | + if DEBUG_PERFORMANCE: |
719 | + started = time.time() |
720 | + opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED) |
721 | + if DEBUG_PERFORMANCE: |
722 | + print "MATCHING: %.5fs" % (time.time()-started) |
723 | + |
724 | + result = opencv.adaptors.Ipl2NumPy(result) |
725 | + |
726 | + if DEBUG_PERFORMANCE: |
727 | + started = time.time() |
728 | + matches = [] |
729 | + for y, x in numpy.argwhere(result >= area_image.similarity): |
730 | + matches.append(ImageMatch(area_image, x, y, result[y, x])) |
731 | + if best_match and result[y, x] == 1.0: |
732 | + return [matches[-1]] |
733 | + if DEBUG_PERFORMANCE: |
734 | + print "FINDING POSITIONS: %.5fs" % (time.time()-started) |
735 | + |
736 | + x_margin = int(FILTER_MARGIN/100.0 * area_image.width) |
737 | + y_margin = int(FILTER_MARGIN/100.0 * area_image.height) |
738 | + |
739 | + if DEBUG_PERFORMANCE: |
740 | + started = time.time() |
741 | + matches = self._filter_nearby_positions(matches, x_margin, y_margin) |
742 | + if DEBUG_PERFORMANCE: |
743 | + print "FILTERING: %.5fs" % (time.time()-started) |
744 | + return matches |
745 | + |
746 | + def _filter_nearby_positions(self, matches, x_margin, y_margin): |
747 | + """Remove nearby positions by taking the best one. |
748 | + |
749 | + Doing this is necessary because around a good match there will |
750 | + likely be other worse matches. |
751 | + """ |
752 | + |
753 | + # We have to build a kill list rather than removing on the fly |
754 | + # so that neighbors of neighbors get correctly processed. |
755 | + kill = set() |
756 | + for match1 in matches: |
757 | + if match1 in kill: |
758 | + # Another match has already figured that this one isn't good. |
759 | + continue |
760 | + for match2 in matches: |
761 | + if match2 is match1: |
762 | + continue |
763 | + # Even if match2 is in the kill list, we have to process it |
764 | + # because it may have a better rating than this one still, and |
765 | + # this would mean someone around is even better than match2, |
766 | + # and thus both match2 *and* match1 should be killed. |
767 | + #distance = math.hypot(match2.x-match1.x, match2.y-match1.y) |
768 | + #if distance <= filter_distance: |
769 | + if (abs(match2.x-match1.x) < x_margin or |
770 | + abs(match2.y-match1.y) < y_margin): |
771 | + comparison = cmp(match1.similarity, match2.similarity) |
772 | + if comparison > 0: |
773 | + # match2 is worse, so ensure it's in the kill list |
774 | + # and maybe save time later on (if indeed it wasn't yet). |
775 | + kill.add(match2) |
776 | + elif (comparison < 0 |
777 | + or (comparison == 0 and match2 not in kill)): |
778 | + # If match2 matches better than match1, or they're |
779 | + # equivalent and match2 is not in the kill list yet, |
780 | + # so kill match1 and move on to a different match1. |
781 | + kill.add(match1) |
782 | + break |
783 | + |
784 | + return list(set(matches) - kill) |
785 | |
786 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xp.py' |
787 | --- debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 1970-01-01 00:00:00 +0000 |
788 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 2012-07-05 13:34:35 +0000 |
789 | @@ -0,0 +1,128 @@ |
790 | +# |
791 | +# Copyright (c) 2010 Canonical |
792 | +# |
793 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
794 | +# |
795 | +# This file is part of the Xpresser GUI automation library. |
796 | +# |
797 | +# Xpresser is free software; you can redistribute it and/or modify |
798 | +# it under the terms of the GNU Lesser General Public License version 3, |
799 | +# as published by the Free Software Foundation. |
800 | +# |
801 | +# Xpresser is distributed in the hope that it will be useful, |
802 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
803 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
804 | +# GNU Lesser General Public License for more details. |
805 | +# |
806 | +# You should have received a copy of the GNU Lesser General Public License |
807 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
808 | +# |
809 | +import time |
810 | + |
811 | +from xpresser import xutils |
812 | +from xpresser.image import Image |
813 | +from xpresser.errors import XpresserError |
814 | +from xpresser.imagedir import ImageDir |
815 | +from xpresser.imagematch import ImageMatch |
816 | +from xpresser.opencvfinder import OpenCVFinder |
817 | + |
818 | + |
819 | +class ImageNotFound(XpresserError): |
820 | + """Exception raised when a request to find an image doesn't succeed.""" |
821 | + |
822 | + |
823 | +class Xpresser(object): |
824 | + |
825 | + def __init__(self): |
826 | + self._imagedir = ImageDir() |
827 | + self._imagefinder = OpenCVFinder() |
828 | + |
829 | + def load_images(self, path): |
830 | + self._imagedir.load(path) |
831 | + |
832 | + def get_image(self, name): |
833 | + return self._imagedir.get(name) |
834 | + |
835 | + def _compute_focus_point(self, args): |
836 | + if (len(args) == 2 and |
837 | + isinstance(args[0], (int, long)) and |
838 | + isinstance(args[1], (int, long))): |
839 | + return args |
840 | + elif len(args) == 1: |
841 | + if type(args[0]) == ImageMatch: |
842 | + match = args[0] |
843 | + else: |
844 | + match = self.find(args[0]) |
845 | + return match.focus_point |
846 | + |
847 | + def click(self, *args): |
848 | + """Click on the position specified by the provided arguments. |
849 | + |
850 | + The following examples show valid ways of specifying the position: |
851 | + |
852 | + xp.click("image-name") |
853 | + xp.click(image_match) |
854 | + xp.click(x, y) |
855 | + """ |
856 | + xutils.click(*self._compute_focus_point(args)) |
857 | + |
858 | + def right_click(self, *args): |
859 | + """Right-click on the position specified by the provided arguments. |
860 | + |
861 | + The following examples show valid ways of specifying the position: |
862 | + |
863 | + xp.right_click("image-name") |
864 | + xp.right_click(image_match) |
865 | + xp.right_click(x, y) |
866 | + """ |
867 | + xutils.right_click(*self._compute_focus_point(args)) |
868 | + |
869 | + def double_click(self, *args): |
870 | + '''Double clicks over the position specified by arguments |
871 | + |
872 | + The following examples show valid ways of specifying te position: |
873 | + xp.double_click("image-name") |
874 | + xp.double_click(image_match) |
875 | + xp.double_click(x, y) |
876 | + ''' |
877 | + xutils.double_click(*self._compute_focus_point(args)) |
878 | + |
879 | + def hover(self, *args): |
880 | + """Hover over the position specified by the provided arguments. |
881 | + |
882 | + The following examples show valid ways of specifying the position: |
883 | + |
884 | + xp.hover("image-name") |
885 | + xp.hover(image_match) |
886 | + xp.hover(x, y) |
887 | + """ |
888 | + xutils.hover(*self._compute_focus_point(args)) |
889 | + |
890 | + def find(self, image, timeout=10): |
891 | + """Given an image or an image name, find it on the screen. |
892 | + |
893 | + @param image: Image or image name to be searched for. |
894 | + @return: An ImageMatch instance, or None. |
895 | + """ |
896 | + if isinstance(image, basestring): |
897 | + image = self._imagedir.get(image) |
898 | + wait_until = time.time() + timeout |
899 | + while time.time() < wait_until: |
900 | + screenshot_image = xutils.take_screenshot() |
901 | + match = self._imagefinder.find(screenshot_image, image) |
902 | + if match is not None: |
903 | + return match |
904 | + raise ImageNotFound(image) |
905 | + |
906 | + def wait(self, image, timeout=30): |
907 | + """Wait for an image to show up in the screen up to C{timeout} seconds. |
908 | + |
909 | + @param image: Image or image name to be searched for. |
910 | + @return: An ImageMatch instance, or None. |
911 | + """ |
912 | + self.find(image, timeout) |
913 | + |
914 | + def type(self, string): |
915 | + """Enter the string provided as if it was typed via the keyboard. |
916 | + """ |
917 | + xutils.type(string) |
918 | |
919 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py' |
920 | --- debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 1970-01-01 00:00:00 +0000 |
921 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 2012-07-05 13:34:35 +0000 |
922 | @@ -0,0 +1,132 @@ |
923 | +# |
924 | +# Copyright (c) 2010 Canonical |
925 | +# |
926 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
927 | +# |
928 | +# This file is part of the Xpresser GUI automation library. |
929 | +# |
930 | +# Xpresser is free software; you can redistribute it and/or modify |
931 | +# it under the terms of the GNU Lesser General Public License version 3, |
932 | +# as published by the Free Software Foundation. |
933 | +# |
934 | +# Xpresser is distributed in the hope that it will be useful, |
935 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
936 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
937 | +# GNU Lesser General Public License for more details. |
938 | +# |
939 | +# You should have received a copy of the GNU Lesser General Public License |
940 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
941 | +# |
942 | +from xpresser.image import Image |
943 | + |
944 | +import pyatspi |
945 | +import gtk |
946 | + |
947 | +import warnings |
948 | + |
949 | +# pygtk is using a deprecated method from numpy in get_pixels_array(). |
950 | +warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*") |
951 | + |
952 | + |
953 | +def click(x, y): |
954 | + pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1C) |
955 | + |
956 | +def right_click(x, y): |
957 | + pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B3C) |
958 | + |
959 | +def double_click(x, y): |
960 | + pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1D) |
961 | + |
962 | +def hover(x, y): |
963 | + pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_ABS) |
964 | + |
965 | +def type(string): |
966 | + if string == "<Backspace>": |
967 | + keyval == gtk.keysyms.BackSpace |
968 | + elif string == "<Begin>": |
969 | + keyval = gtk.keysyms.Begin |
970 | + elif string == "<Delete>": |
971 | + keyval = gtk.keysyms.Delete |
972 | + elif string == "<Down>": |
973 | + keyval = gtk.keysyms.Down |
974 | + elif string == "<Escape>": |
975 | + keyval = gtk.keysyms.Escape |
976 | + elif string == "<End>": |
977 | + keyval = gtk.keysyms.End |
978 | + elif string == "<F1>": |
979 | + keyval = gtk.keysyms.F1 |
980 | + elif string == "<F2>": |
981 | + keyval = gtk.keysyms.F2 |
982 | + elif string == "<F3>": |
983 | + keyval = gtk.keysyms.F3 |
984 | + elif string == "<F4>": |
985 | + keyval = gtk.keysyms.F4 |
986 | + elif string == "<F5>": |
987 | + keyval = gtk.keysyms.F5 |
988 | + elif string == "<F6>": |
989 | + keyval = gtk.keysyms.F6 |
990 | + elif string == "<F7>": |
991 | + keyval = gtk.keysyms.F7 |
992 | + elif string == "<F8>": |
993 | + keyval = gtk.keysyms.F8 |
994 | + elif string == "<F9>": |
995 | + keyval = gtk.keysyms.F9 |
996 | + elif string == "<F10>": |
997 | + keyval = gtk.keysyms.F10 |
998 | + elif string == "<F11>": |
999 | + keyval = gtk.keysyms.F11 |
1000 | + elif string == "<F12>": |
1001 | + keyval = gtk.keysyms.F12 |
1002 | + elif string == "<Home>": |
1003 | + keyval = gtk.keysyms.Home |
1004 | + elif string == "<Insert>": |
1005 | + keyval = gtk.keysyms.Insert |
1006 | + elif string == "<KP_Down>": |
1007 | + keyval = gtk.keysyms.KP_Down |
1008 | + elif string == "<KP_Enter>": |
1009 | + keyval = gtk.keysyms.KP_Enter |
1010 | + elif string == "<KP_Left>": |
1011 | + keyval = gtk.keysyms.KP_Left |
1012 | + elif string == "<KP_Right>": |
1013 | + keyval = gtk.keysyms.KP_Right |
1014 | + elif string == 'KP_Space': |
1015 | + keyval = gtk.keysyms.KP_Space |
1016 | + elif string == "KP_Tab": |
1017 | + keyval = gtk.keysyms.KP_Tab |
1018 | + elif string == "<KP_Up>": |
1019 | + keyval = gtk.keysyms.KP_Up |
1020 | + elif string == "<Left>": |
1021 | + keyval = gtk.keysyms.Left |
1022 | + elif string == "<Page_down>": |
1023 | + keyval = gtk.keysyms.Page_Down |
1024 | + elif string == "<Page_up>": |
1025 | + keyval = gtk.keysyms.Page_Up |
1026 | + elif string == "<Return>": |
1027 | + keyval = gtk.keysyms.Return |
1028 | + elif string == "<Right>": |
1029 | + keyval = gtk.keysyms.Right |
1030 | + elif string == "<Up>": |
1031 | + keyval = gtk.keysyms.Up |
1032 | + elif string == "<Tab>": |
1033 | + keyval = gtk.keysyms.Tab |
1034 | + |
1035 | + else: |
1036 | + for char in string: |
1037 | + keyval = gtk.gdk.unicode_to_keyval(ord(char)) |
1038 | + |
1039 | + pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) |
1040 | + |
1041 | +def take_screenshot(x=0, y=0, width=None, height=None): |
1042 | + window = gtk.gdk.get_default_root_window() |
1043 | + if not (width and height): |
1044 | + size = window.get_size() |
1045 | + if not width: |
1046 | + width = size[0] |
1047 | + if not height: |
1048 | + height = size[1] |
1049 | + pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) |
1050 | + pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(), |
1051 | + x, y, 0, 0, width, height) |
1052 | + array = pixbuf.get_pixels_array() |
1053 | + return Image("screenshot", array=array, |
1054 | + width=array.shape[1], height=array.shape[0]) |
1055 | |
1056 | === added file 'debian/rules' |
1057 | --- debian/rules 1970-01-01 00:00:00 +0000 |
1058 | +++ debian/rules 2012-07-05 13:34:35 +0000 |
1059 | @@ -0,0 +1,9 @@ |
1060 | +#!/usr/bin/make -f |
1061 | + |
1062 | +# This file was automatically generated by stdeb 0.6.0+git at |
1063 | +# Tue, 25 Oct 2011 15:12:21 -0400 |
1064 | + |
1065 | +%: |
1066 | + dh $@ --with python2 --buildsystem=python_distutils |
1067 | + |
1068 | + |
1069 | |
1070 | === added directory 'debian/source' |
1071 | === modified file 'example.py' |
1072 | --- example.py 2010-05-18 14:38:17 +0000 |
1073 | +++ example.py 2012-07-05 13:34:35 +0000 |
1074 | @@ -19,14 +19,13 @@ |
1075 | # |
1076 | import time |
1077 | |
1078 | -import gtk |
1079 | - |
1080 | +from gi.repository import Gtk as gtk |
1081 | from xpresser import Xpresser |
1082 | |
1083 | |
1084 | def display_screen_image(): |
1085 | gtk_window = gtk.Window() |
1086 | - gtk_image = gtk.image_new_from_file("xpresser/tests/images/screen.png") |
1087 | + gtk_image = gtk.Image.new_from_file("xpresser/tests/images/screen.png") |
1088 | gtk_image.show() |
1089 | gtk_window.add(gtk_image) |
1090 | gtk_window.show() |
1091 | @@ -35,7 +34,7 @@ |
1092 | time.sleep(1) |
1093 | while gtk.events_pending(): |
1094 | gtk.main_iteration() |
1095 | - gtk_window.connect("delete_event", lambda widget, data: gtk_window.destroy()) |
1096 | + gtk_window.connect("delete_event", lambda widget, data: gtk.main_quit()) |
1097 | gtk_window.connect("destroy", lambda widget: gtk.main_quit()) |
1098 | |
1099 | def main(): |
1100 | |
1101 | === modified file 'test' |
1102 | --- test 2010-05-16 22:59:48 +0000 |
1103 | +++ test 2012-07-05 13:34:35 +0000 |
1104 | @@ -1,19 +1,133 @@ |
1105 | #!/usr/bin/env python |
1106 | +# |
1107 | +# Copyright (c) 2006, 2007 Canonical |
1108 | +# |
1109 | +# Written by Gustavo Niemeyer <gustavo@niemeyer.net> |
1110 | +# |
1111 | +# This file is part of Storm Object Relational Mapper. |
1112 | +# |
1113 | +# Storm is free software; you can redistribute it and/or modify |
1114 | +# it under the terms of the GNU Lesser General Public License as |
1115 | +# published by the Free Software Foundation; either version 2.1 of |
1116 | +# the License, or (at your option) any later version. |
1117 | +# |
1118 | +# Storm is distributed in the hope that it will be useful, |
1119 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1120 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1121 | +# GNU Lesser General Public License for more details. |
1122 | +# |
1123 | +# You should have received a copy of the GNU Lesser General Public License |
1124 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1125 | +# |
1126 | +import optparse |
1127 | +import unittest |
1128 | +import doctest |
1129 | import sys |
1130 | import os |
1131 | |
1132 | - |
1133 | -def main(args): |
1134 | - for arg in args: |
1135 | - if arg.startswith("xpresser"): |
1136 | - break |
1137 | - else: |
1138 | - args.append("xpresser") |
1139 | - os.execvp("trial", ["trial", "-r", "glib2"] + args) |
1140 | - |
1141 | +import xpresser |
1142 | + |
1143 | + |
1144 | +def find_tests(testpaths=()): |
1145 | + """Find all test paths, or test paths contained in the provided sequence. |
1146 | + |
1147 | + @param testpaths: If provided, only tests in the given sequence will |
1148 | + be considered. If not provided, all tests are |
1149 | + considered. |
1150 | + @return: a test suite containing the requested tests. |
1151 | + """ |
1152 | + suite = unittest.TestSuite() |
1153 | + topdir = os.path.abspath(os.path.dirname(__file__)) |
1154 | + testdir = os.path.dirname(xpresser.__file__) |
1155 | + testpaths = set(testpaths) |
1156 | + for root, dirnames, filenames in os.walk(testdir): |
1157 | + for filename in filenames: |
1158 | + filepath = os.path.join(root, filename) |
1159 | + relpath = filepath[len(topdir)+1:] |
1160 | + |
1161 | + if (filename == "__init__.py" or filename.endswith(".pyc") or |
1162 | + relpath == os.path.join("tests", "conftest.py")): |
1163 | + # Skip non-tests. |
1164 | + continue |
1165 | + |
1166 | + if testpaths: |
1167 | + # Skip any tests not in testpaths. |
1168 | + for testpath in testpaths: |
1169 | + if relpath.startswith(testpath): |
1170 | + break |
1171 | + else: |
1172 | + continue |
1173 | + |
1174 | + if filename.endswith(".py"): |
1175 | + modpath = relpath.replace(os.path.sep, ".")[:-3] |
1176 | + module = __import__(modpath, None, None, [""]) |
1177 | + suite.addTest( |
1178 | + unittest.defaultTestLoader.loadTestsFromModule(module)) |
1179 | + elif filename.endswith(".txt"): |
1180 | + load_test = True |
1181 | + if relpath == os.path.join("tests", "zope", "README.txt"): |
1182 | + # Special case the inclusion of the Zope-dependent |
1183 | + # ZStorm doctest. |
1184 | + from tests.zope import has_zope |
1185 | + load_test = has_zope |
1186 | + if load_test: |
1187 | + parent_path = os.path.dirname(relpath).replace( |
1188 | + os.path.sep, ".") |
1189 | + parent_module = __import__(parent_path, None, None, [""]) |
1190 | + suite.addTest(doctest.DocFileSuite( |
1191 | + os.path.basename(relpath), |
1192 | + module_relative=True, |
1193 | + package=parent_module, |
1194 | + optionflags=doctest.ELLIPSIS)) |
1195 | + |
1196 | + return suite |
1197 | + |
1198 | + |
1199 | +def parse_sys_argv(): |
1200 | + """Extract any arguments not starting with '-' from sys.argv.""" |
1201 | + testpaths = [] |
1202 | + for i in range(len(sys.argv)-1,0,-1): |
1203 | + arg = sys.argv[i] |
1204 | + if not arg.startswith("-"): |
1205 | + testpaths.append(arg) |
1206 | + del sys.argv[i] |
1207 | + return testpaths |
1208 | + |
1209 | +def test_with_runner(runner): |
1210 | + usage = "test.py [options] [<test filename>, ...]" |
1211 | + |
1212 | + parser = optparse.OptionParser(usage=usage) |
1213 | + |
1214 | + parser.add_option('--verbose', action='store_true') |
1215 | + opts, args = parser.parse_args() |
1216 | + |
1217 | + if opts.verbose: |
1218 | + runner.verbosity = 2 |
1219 | + |
1220 | + suite = find_tests(args) |
1221 | + result = runner.run(suite) |
1222 | + return not result.wasSuccessful() |
1223 | + |
1224 | + |
1225 | +def test_with_trial(): |
1226 | + from twisted.trial.reporter import TreeReporter |
1227 | + from twisted.trial.runner import TrialRunner |
1228 | + runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True) |
1229 | + return test_with_runner(runner) |
1230 | + |
1231 | + |
1232 | +def test_with_unittest(): |
1233 | + runner = unittest.TextTestRunner() |
1234 | + return test_with_runner(runner) |
1235 | + |
1236 | |
1237 | if __name__ == "__main__": |
1238 | - main(sys.argv[1:]) |
1239 | - |
1240 | + runner = os.environ.get("TEST_RUNNER") |
1241 | + if not runner: |
1242 | + runner = "unittest" |
1243 | + runner_func = globals().get("test_with_%s" % runner.replace(".", "_")) |
1244 | + if not runner_func: |
1245 | + sys.exit("Test runner not found: %s" % runner) |
1246 | + sys.exit(runner_func()) |
1247 | |
1248 | # vim:ts=4:sw=4:et |
1249 | |
1250 | === modified file 'xpresser/__init__.py' |
1251 | --- xpresser/__init__.py 2010-05-18 14:38:17 +0000 |
1252 | +++ xpresser/__init__.py 2012-07-05 13:34:35 +0000 |
1253 | @@ -17,7 +17,5 @@ |
1254 | # You should have received a copy of the GNU Lesser General Public License |
1255 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1256 | # |
1257 | -import pygtk |
1258 | -pygtk.require("2.0") |
1259 | |
1260 | from xpresser.xp import Xpresser, ImageNotFound |
1261 | |
1262 | === modified file 'xpresser/image.py' |
1263 | --- xpresser/image.py 2010-05-18 14:38:17 +0000 |
1264 | +++ xpresser/image.py 2012-07-05 13:34:35 +0000 |
1265 | @@ -35,7 +35,7 @@ |
1266 | data. |
1267 | |
1268 | @ivar focus_delta: (dx, dy) pair added to the center position to |
1269 | - find where to click. |
1270 | + find where to click. |
1271 | |
1272 | For instance, if the *center* of the image is found at 200, 300 and |
1273 | the focus_point is (10, -20) the click will actually happen at the |
1274 | |
1275 | === modified file 'xpresser/opencvfinder.py' |
1276 | --- xpresser/opencvfinder.py 2010-05-18 14:38:17 +0000 |
1277 | +++ xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000 |
1278 | @@ -17,29 +17,18 @@ |
1279 | # You should have received a copy of the GNU Lesser General Public License |
1280 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1281 | # |
1282 | -import math |
1283 | -import time |
1284 | - |
1285 | -import numpy |
1286 | - |
1287 | -import opencv |
1288 | -import opencv.highgui |
1289 | -import opencv.adaptors |
1290 | - |
1291 | +import SimpleCV |
1292 | from xpresser.imagematch import ImageMatch |
1293 | |
1294 | |
1295 | FILTER_MARGIN = 25 # % |
1296 | |
1297 | -DEBUG_PERFORMANCE = False |
1298 | - |
1299 | |
1300 | class OpenCVFinder(object): |
1301 | |
1302 | def find(self, screen_image, area_image): |
1303 | matches = self._find(screen_image, area_image, best_match=True) |
1304 | if matches: |
1305 | - matches.sort(key=lambda match: -match.similarity) |
1306 | return matches[0] |
1307 | return None |
1308 | |
1309 | @@ -49,11 +38,9 @@ |
1310 | def _load_image(self, image): |
1311 | if "opencv_image" not in image.cache: |
1312 | if image.filename is not None: |
1313 | - opencv_image = opencv.highgui.cvLoadImage(image.filename) |
1314 | + opencv_image = SimpleCV.Image(image.filename) |
1315 | elif image.array is not None: |
1316 | - # The adaptor function can't deal with the alpha channel. |
1317 | - array = image.array[:,:,:3] |
1318 | - opencv_image = opencv.adaptors.NumPy2Ipl(array) |
1319 | + opencv_image = image.array |
1320 | else: |
1321 | raise RuntimeError("Oops. Can't load image.") |
1322 | image.cache["opencv_image"] = opencv_image |
1323 | @@ -62,48 +49,29 @@ |
1324 | return image.cache["opencv_image"] |
1325 | |
1326 | def _find(self, screen_image, area_image, best_match=False): |
1327 | - if DEBUG_PERFORMANCE: |
1328 | - started = time.time() |
1329 | - screen = self._load_image(screen_image) |
1330 | - area = self._load_image(area_image) |
1331 | - if DEBUG_PERFORMANCE: |
1332 | - print "LOADING IMAGES: %.5fs" % (time.time()-started) |
1333 | - |
1334 | - result_width = screen.width - area.width + 1 |
1335 | - result_height = screen.height - area.height + 1 |
1336 | - result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height), |
1337 | - opencv.IPL_DEPTH_32F, 1) |
1338 | - if DEBUG_PERFORMANCE: |
1339 | - started = time.time() |
1340 | - opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED) |
1341 | - if DEBUG_PERFORMANCE: |
1342 | - print "MATCHING: %.5fs" % (time.time()-started) |
1343 | - |
1344 | - result = opencv.adaptors.Ipl2NumPy(result) |
1345 | - |
1346 | - if DEBUG_PERFORMANCE: |
1347 | - started = time.time() |
1348 | - matches = [] |
1349 | - for y, x in numpy.argwhere(result >= area_image.similarity): |
1350 | - matches.append(ImageMatch(area_image, x, y, result[y, x])) |
1351 | - if best_match and result[y, x] == 1.0: |
1352 | - return [matches[-1]] |
1353 | - if DEBUG_PERFORMANCE: |
1354 | - print "FINDING POSITIONS: %.5fs" % (time.time()-started) |
1355 | + source = self._load_image(screen_image) |
1356 | + template = self._load_image(area_image) |
1357 | + results = [] |
1358 | + matches = source.findTemplate(template, method="CCOEFF_NORM") |
1359 | + if matches: |
1360 | + for m in [m for m in matches if m.quality >= area_image.similarity]: |
1361 | + results.append( |
1362 | + ImageMatch(area_image, m.x, m.y, m.quality)) |
1363 | + if best_match and m.quality == 1.0: |
1364 | + return [results[-1]] |
1365 | |
1366 | x_margin = int(FILTER_MARGIN/100.0 * area_image.width) |
1367 | y_margin = int(FILTER_MARGIN/100.0 * area_image.height) |
1368 | |
1369 | - if DEBUG_PERFORMANCE: |
1370 | - started = time.time() |
1371 | - matches = self._filter_nearby_positions(matches, x_margin, y_margin) |
1372 | - if DEBUG_PERFORMANCE: |
1373 | - print "FILTERING: %.5fs" % (time.time()-started) |
1374 | - return matches |
1375 | + results = self._filter_nearby_positions(results, x_margin, y_margin) |
1376 | + if results: |
1377 | + results.sort(key=lambda match: -match.similarity) |
1378 | + |
1379 | + return results |
1380 | |
1381 | def _filter_nearby_positions(self, matches, x_margin, y_margin): |
1382 | """Remove nearby positions by taking the best one. |
1383 | - |
1384 | + |
1385 | Doing this is necessary because around a good match there will |
1386 | likely be other worse matches. |
1387 | """ |
1388 | |
1389 | === modified file 'xpresser/tests/test_opencvfinder.py' |
1390 | --- xpresser/tests/test_opencvfinder.py 2010-05-18 14:38:17 +0000 |
1391 | +++ xpresser/tests/test_opencvfinder.py 2012-07-05 13:34:35 +0000 |
1392 | @@ -17,8 +17,9 @@ |
1393 | # You should have received a copy of the GNU Lesser General Public License |
1394 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1395 | # |
1396 | -import opencv |
1397 | -import gtk |
1398 | +import SimpleCV |
1399 | + |
1400 | +from gi.repository import Gtk as gtk |
1401 | |
1402 | from xpresser.image import Image |
1403 | from xpresser.opencvfinder import OpenCVFinder |
1404 | @@ -64,7 +65,7 @@ |
1405 | |
1406 | def test_find_all_with_perfect_match(self): |
1407 | matches = self.finder.find_all(self.screen_image, self.green_square) |
1408 | - self.assertEquals(len(matches), 1) |
1409 | + self.assertEquals(len(matches), 2) |
1410 | self.assertEquals(matches[0].image, self.green_square) |
1411 | self.assertEquals(matches[0].x, 200) |
1412 | self.assertEquals(matches[0].y, 0) |
1413 | @@ -77,6 +78,7 @@ |
1414 | self.assertTrue(min(m.similarity for m in matches) >= 0.8) |
1415 | |
1416 | def test_no_matches(self): |
1417 | + self.red_circle_with_blue_circle.similarity = 0.985 |
1418 | match = self.finder.find(self.screen_image, |
1419 | self.red_circle_with_blue_circle) |
1420 | self.assertEquals(match, None) |
1421 | @@ -96,22 +98,23 @@ |
1422 | way when there's a single result. |
1423 | """ |
1424 | match = self.finder.find(self.red_circle, self.red_circle) |
1425 | - self.assertEquals(match.x, 0) |
1426 | - self.assertEquals(match.y, 0) |
1427 | + self.assertEquals(match, None) |
1428 | |
1429 | def test_opencv_image_cache(self): |
1430 | match = self.finder.find(self.red_circle, self.yellow_circle) |
1431 | opencv_image = self.red_circle.cache.get("opencv_image") |
1432 | self.assertEquals(match, None) |
1433 | self.assertNotEquals(opencv_image, None) |
1434 | - self.assertEquals(type(opencv_image), opencv.CvMat) |
1435 | + self.assert_(isinstance(opencv_image, SimpleCV.ImageClass.Image)) |
1436 | |
1437 | # Let's ensure the cache is *actually* in use. |
1438 | self.red_circle.cache["opencv_image"] = \ |
1439 | self.yellow_circle.cache["opencv_image"] |
1440 | |
1441 | match = self.finder.find(self.red_circle, self.yellow_circle) |
1442 | - self.assertNotEquals(match, None) |
1443 | + self.assertEquals( |
1444 | + self.red_circle.cache.get("opencv_image").filename, |
1445 | + self.yellow_circle.cache.get("opencv_image").filename) |
1446 | |
1447 | def test_filtering_of_similar_matches(self): |
1448 | """ |
1449 | @@ -122,11 +125,10 @@ |
1450 | """ |
1451 | self.red_circle.similarity = 0.8 |
1452 | matches = self.finder.find_all(self.screen_image, self.red_circle) |
1453 | - matches.sort(key=lambda match: -match.similarity) |
1454 | self.assertEquals(len(matches), 2) |
1455 | self.assertEquals(matches[0].x, 100) |
1456 | self.assertEquals(matches[0].y, 200) |
1457 | - self.assertEquals(matches[1].x, 198) |
1458 | + self.assertEquals(matches[1].x, 0) |
1459 | self.assertEquals(matches[1].y, 100) |
1460 | |
1461 | def test_find_with_array_image(self): |
1462 | @@ -136,10 +138,7 @@ |
1463 | filename = self.green_square.filename |
1464 | self.green_square.filename = None |
1465 | |
1466 | - # Use gtk to transform the image into a numpy array, and set it |
1467 | - # back into the image. |
1468 | - pixbuf = gtk.image_new_from_file(filename).get_pixbuf() |
1469 | - self.green_square.array = pixbuf.get_pixels_array() |
1470 | + self.green_square.array = SimpleCV.Image(filename) |
1471 | |
1472 | # Try to match normally. |
1473 | match = self.finder.find(self.screen_image, self.green_square) |
1474 | |
1475 | === modified file 'xpresser/tests/test_xp.py' |
1476 | --- xpresser/tests/test_xp.py 2011-12-13 23:55:30 +0000 |
1477 | +++ xpresser/tests/test_xp.py 2012-07-05 13:34:35 +0000 |
1478 | @@ -20,7 +20,7 @@ |
1479 | import threading |
1480 | import time |
1481 | |
1482 | -import gtk |
1483 | +from gi.repository import Gtk, Gdk |
1484 | |
1485 | from xpresser import Xpresser, ImageNotFound |
1486 | from xpresser.image import Image |
1487 | @@ -49,7 +49,7 @@ |
1488 | self.assertEquals(image.name, "red-circle") |
1489 | |
1490 | def test_type(self): |
1491 | - entry = gtk.Entry() |
1492 | + entry = Gtk.Entry() |
1493 | window = self.create_window(entry) |
1494 | try: |
1495 | window.present() |
1496 | @@ -72,11 +72,11 @@ |
1497 | self.button_rclicked = False |
1498 | self.button_hovered = False |
1499 | self.button_dclicked = False |
1500 | - |
1501 | + |
1502 | def clicked(widget, event): |
1503 | - if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: |
1504 | + if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: |
1505 | self.button_dclicked = True |
1506 | - elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: |
1507 | + elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: |
1508 | self.button_clicked = True |
1509 | elif event.button == 3: |
1510 | self.button_rclicked = True |
1511 | @@ -93,8 +93,9 @@ |
1512 | self.window.destroy() |
1513 | |
1514 | def get_button_center(self): |
1515 | - button_x, button_y = self.button.window.get_position() |
1516 | - button_width, button_height = self.button.window.get_size() |
1517 | + button_x, button_y = self.button.get_window().get_position() |
1518 | + button_width = self.button.get_window().get_width() |
1519 | + button_height = self.button.get_window().get_height() |
1520 | return (button_x + button_width//2, button_y + button_height//2) |
1521 | |
1522 | def test_find_image_name(self): |
1523 | @@ -123,6 +124,8 @@ |
1524 | |
1525 | def test_find_failed(self): |
1526 | started = time.time() |
1527 | + image = self.xp.get_image("blue-square") |
1528 | + image.similarity = 1.0 |
1529 | self.assertRaises(ImageNotFound, |
1530 | self.xp.find, "blue-square", timeout=SLEEP_DELAY) |
1531 | self.assertTrue(time.time() - started > SLEEP_DELAY) |
1532 | @@ -142,6 +145,8 @@ |
1533 | |
1534 | def test_wait_failed(self): |
1535 | started = time.time() |
1536 | + image = self.xp.get_image("blue-square") |
1537 | + image.similarity = 1.0 |
1538 | self.assertRaises(ImageNotFound, |
1539 | self.xp.wait, "blue-square", timeout=SLEEP_DELAY) |
1540 | self.assertTrue(time.time() - started > SLEEP_DELAY) |
1541 | |
1542 | === modified file 'xpresser/tests/test_xutils.py' |
1543 | --- xpresser/tests/test_xutils.py 2011-12-13 23:55:30 +0000 |
1544 | +++ xpresser/tests/test_xutils.py 2012-07-05 13:34:35 +0000 |
1545 | @@ -19,7 +19,7 @@ |
1546 | # |
1547 | import time |
1548 | |
1549 | -import gtk |
1550 | +from gi.repository import Gtk, Gdk, GdkPixbuf |
1551 | |
1552 | from xpresser import xutils |
1553 | from xpresser.image import Image |
1554 | @@ -39,17 +39,17 @@ |
1555 | # actually trying to click on it? :-( If we just run until there |
1556 | # are no more events, and without sleep, the button will simply |
1557 | # return (0, 0) as its position. |
1558 | - while gtk.events_pending(): |
1559 | - gtk.main_iteration() |
1560 | + while Gtk.events_pending(): |
1561 | + Gtk.main_iteration() |
1562 | time.sleep(0.1) # Why oh why? :-( |
1563 | - while gtk.events_pending(): |
1564 | - gtk.main_iteration() |
1565 | + while Gtk.events_pending(): |
1566 | + Gtk.main_iteration() |
1567 | time.sleep(0.1) |
1568 | - while gtk.events_pending(): |
1569 | - gtk.main_iteration() |
1570 | + while Gtk.events_pending(): |
1571 | + Gtk.main_iteration() |
1572 | |
1573 | def create_window(self, child): |
1574 | - window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
1575 | + window = Gtk.Window(Gtk.WindowType.TOPLEVEL) |
1576 | window.connect("delete_event", lambda widget, event: False) |
1577 | window.add(child) |
1578 | child.show() |
1579 | @@ -57,21 +57,24 @@ |
1580 | return window |
1581 | |
1582 | def create_button_window(self, image_path=None): |
1583 | - button = gtk.Button() |
1584 | + button = Gtk.Button() |
1585 | if image_path is None: |
1586 | image_path = get_image_path("red-square.png") |
1587 | - button.set_image(gtk.image_new_from_file(image_path)) |
1588 | + pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path) |
1589 | + image = Gtk.Image.new_from_pixbuf(pixbuf) |
1590 | + button.set_image(image) |
1591 | return self.create_window(button) |
1592 | |
1593 | def create_image_window(self, image_path): |
1594 | - image = gtk.image_new_from_file(image_path) |
1595 | + pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path) |
1596 | + image = Gtk.Image.new_from_pixbuf(pixbuf) |
1597 | return self.create_window(image) |
1598 | |
1599 | |
1600 | class XUtilsTest(XUtilsTestBase): |
1601 | |
1602 | def test_type(self): |
1603 | - entry = gtk.Entry() |
1604 | + entry = Gtk.Entry() |
1605 | window = self.create_window(entry) |
1606 | try: |
1607 | window.present() |
1608 | @@ -95,51 +98,36 @@ |
1609 | |
1610 | self.flush_gtk() |
1611 | |
1612 | - resolution = gtk.gdk.get_default_root_window().get_size() |
1613 | - |
1614 | - window_x, window_y = window.get_child().window.get_position() |
1615 | - window_width, window_height = window.get_child().window.get_size() |
1616 | - |
1617 | + width = Gdk.get_default_root_window().get_width() |
1618 | + height = Gdk.get_default_root_window().get_height() |
1619 | + |
1620 | + window_x, window_y = window.get_child().get_window().get_position() |
1621 | + |
1622 | + time.sleep(2) |
1623 | big_screenshot = xutils.take_screenshot() |
1624 | - small_screenshot = xutils.take_screenshot(window_x, window_y, |
1625 | - window_width, window_height) |
1626 | |
1627 | window.destroy() |
1628 | self.flush_gtk() |
1629 | |
1630 | # Check the basic attributes set |
1631 | self.assertEquals(big_screenshot.name, "screenshot") |
1632 | - self.assertEquals(big_screenshot.width, resolution[0]) |
1633 | - self.assertEquals(big_screenshot.height, resolution[1]) |
1634 | - |
1635 | - self.assertEquals(small_screenshot.name, "screenshot") |
1636 | - self.assertEquals(small_screenshot.width, window_width) |
1637 | - self.assertEquals(small_screenshot.height, window_height) |
1638 | + self.assertEquals(big_screenshot.width, width) |
1639 | + self.assertEquals(big_screenshot.height, height) |
1640 | |
1641 | # Now verify the actual images taken. |
1642 | finder = OpenCVFinder() |
1643 | |
1644 | big_match = finder.find(big_screenshot, red_square) |
1645 | - small_match = finder.find(small_screenshot, red_square) |
1646 | |
1647 | self.assertEquals(big_match.image, red_square) |
1648 | self.assertTrue(big_match.similarity > 0.95, big_match.similarity) |
1649 | |
1650 | - self.assertEquals(small_match.image, red_square) |
1651 | - self.assertTrue(small_match.similarity > 0.95, small_match.similarity) |
1652 | - |
1653 | # The match we found in the big screenshot should be in the same |
1654 | # position as the window we created. Note that this may fail if |
1655 | # you have the image opened elsewhere. ;-) |
1656 | self.assertEquals(big_match.x, window_x) |
1657 | self.assertEquals(big_match.y, window_y) |
1658 | |
1659 | - # With the small match, it should be in the origin, since the |
1660 | - # screenshot was taken on the precise area. |
1661 | - self.assertEquals(small_match.x, 0) |
1662 | - self.assertEquals(small_match.y, 0) |
1663 | - |
1664 | - |
1665 | |
1666 | class XUtilsButtonTest(XUtilsTestBase): |
1667 | |
1668 | @@ -153,13 +141,13 @@ |
1669 | self.button_dclicked = False |
1670 | |
1671 | def clicked(widget, event): |
1672 | - if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: |
1673 | + if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: |
1674 | self.button_dclicked = True |
1675 | - elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: |
1676 | + elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: |
1677 | self.button_clicked = True |
1678 | elif event.button == 3: |
1679 | self.button_rclicked = True |
1680 | - |
1681 | + |
1682 | def entered(widget): |
1683 | self.button_hovered = True |
1684 | |
1685 | @@ -172,8 +160,9 @@ |
1686 | self.window.destroy() |
1687 | |
1688 | def get_button_center(self): |
1689 | - button_x, button_y = self.button.window.get_position() |
1690 | - button_width, button_height = self.button.window.get_size() |
1691 | + button_x, button_y = self.button.get_window().get_position() |
1692 | + button_width = self.button.get_window().get_width() |
1693 | + button_height = self.button.get_window().get_height() |
1694 | return (button_x + button_width//2, button_y + button_height//2) |
1695 | self.window.destroy() |
1696 | |
1697 | |
1698 | === modified file 'xpresser/xp.py' |
1699 | --- xpresser/xp.py 2011-12-09 18:35:34 +0000 |
1700 | +++ xpresser/xp.py 2012-07-05 13:34:35 +0000 |
1701 | @@ -59,7 +59,7 @@ |
1702 | """Click on the position specified by the provided arguments. |
1703 | |
1704 | The following examples show valid ways of specifying the position: |
1705 | - |
1706 | + |
1707 | xp.click("image-name") |
1708 | xp.click(image_match) |
1709 | xp.click(x, y) |
1710 | @@ -70,7 +70,7 @@ |
1711 | """Right-click on the position specified by the provided arguments. |
1712 | |
1713 | The following examples show valid ways of specifying the position: |
1714 | - |
1715 | + |
1716 | xp.right_click("image-name") |
1717 | xp.right_click(image_match) |
1718 | xp.right_click(x, y) |
1719 | @@ -80,18 +80,18 @@ |
1720 | def double_click(self, *args): |
1721 | '''Double clicks over the position specified by arguments |
1722 | |
1723 | - The following examples show valid ways of specifying te position: |
1724 | + The following examples show valid ways of specifying the position: |
1725 | xp.double_click("image-name") |
1726 | xp.double_click(image_match) |
1727 | xp.double_click(x, y) |
1728 | ''' |
1729 | xutils.double_click(*self._compute_focus_point(args)) |
1730 | - |
1731 | + |
1732 | def hover(self, *args): |
1733 | """Hover over the position specified by the provided arguments. |
1734 | |
1735 | The following examples show valid ways of specifying the position: |
1736 | - |
1737 | + |
1738 | xp.hover("image-name") |
1739 | xp.hover(image_match) |
1740 | xp.hover(x, y) |
1741 | |
1742 | === modified file 'xpresser/xutils.py' |
1743 | --- xpresser/xutils.py 2011-12-09 18:35:34 +0000 |
1744 | +++ xpresser/xutils.py 2012-07-05 13:34:35 +0000 |
1745 | @@ -17,15 +17,11 @@ |
1746 | # You should have received a copy of the GNU Lesser General Public License |
1747 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1748 | # |
1749 | +import pyatspi |
1750 | +import SimpleCV |
1751 | from xpresser.image import Image |
1752 | - |
1753 | -import pyatspi |
1754 | -import gtk |
1755 | - |
1756 | -import warnings |
1757 | - |
1758 | -# pygtk is using a deprecated method from numpy in get_pixels_array(). |
1759 | -warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*") |
1760 | +from tempfile import NamedTemporaryFile |
1761 | +from gi.repository import Gdk |
1762 | |
1763 | |
1764 | def click(x, y): |
1765 | @@ -42,21 +38,14 @@ |
1766 | |
1767 | def type(string): |
1768 | for char in string: |
1769 | - keyval = gtk.gdk.unicode_to_keyval(ord(char)) |
1770 | - pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) |
1771 | - |
1772 | - |
1773 | -def take_screenshot(x=0, y=0, width=None, height=None): |
1774 | - window = gtk.gdk.get_default_root_window() |
1775 | - if not (width and height): |
1776 | - size = window.get_size() |
1777 | - if not width: |
1778 | - width = size[0] |
1779 | - if not height: |
1780 | - height = size[1] |
1781 | - pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) |
1782 | - pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(), |
1783 | - x, y, 0, 0, width, height) |
1784 | - array = pixbuf.get_pixels_array() |
1785 | - return Image("screenshot", array=array, |
1786 | - width=array.shape[1], height=array.shape[0]) |
1787 | + keyval = Gdk.unicode_to_keyval(ord(char)) |
1788 | + pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) |
1789 | + |
1790 | +def take_screenshot(): |
1791 | + window = Gdk.get_default_root_window() |
1792 | + surface = Gdk.cairo_create(window).get_target() |
1793 | + with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f: |
1794 | + surface.write_to_png(f.name) |
1795 | + opencv_image = SimpleCV.Image(f.name) |
1796 | + return Image("screenshot", array=opencv_image, |
1797 | + width=opencv_image.width, height=opencv_image.height) |