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
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'
309Binary 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)

Subscribers

People subscribed via source and target branches

to all changes: