aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--COPYING340
-rw-r--r--HACKING11
-rw-r--r--INSTALL9
-rw-r--r--MANIFEST146
-rw-r--r--MANIFEST.in12
-rw-r--r--Makefile52
-rw-r--r--NEWS597
-rw-r--r--PKG-INFO13
-rw-r--r--README74
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/__init__.py16
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/config.py219
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/distro.py362
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/dump.py33
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/errors.py28
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/i18n.py281
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/logging.py218
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/path.py113
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/predicate.py443
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/procedural.py455
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/rawinput.py252
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/sessions.py231
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/tc.py230
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/tree.py1318
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/utils.py395
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/version.py56
-rw-r--r--build/lib.linux-x86_64-2.7/dogtail/wrapped.py33
-rw-r--r--build/lib/dogtail/__init__.py16
-rw-r--r--build/lib/dogtail/config.py219
-rw-r--r--build/lib/dogtail/distro.py362
-rw-r--r--build/lib/dogtail/dump.py33
-rw-r--r--build/lib/dogtail/errors.py28
-rw-r--r--build/lib/dogtail/i18n.py281
-rw-r--r--build/lib/dogtail/logging.py218
-rw-r--r--build/lib/dogtail/path.py113
-rw-r--r--build/lib/dogtail/predicate.py443
-rw-r--r--build/lib/dogtail/procedural.py455
-rw-r--r--build/lib/dogtail/rawinput.py252
-rw-r--r--build/lib/dogtail/sessions.py231
-rw-r--r--build/lib/dogtail/tc.py230
-rw-r--r--build/lib/dogtail/tree.py1318
-rw-r--r--build/lib/dogtail/utils.py395
-rw-r--r--build/lib/dogtail/version.py56
-rw-r--r--build/lib/dogtail/wrapped.py33
-rwxr-xr-xbuild/scripts-2.7/dogtail-detect-session66
-rwxr-xr-xbuild/scripts-2.7/dogtail-logout28
-rwxr-xr-xbuild/scripts-2.7/dogtail-run-headless79
-rwxr-xr-xbuild/scripts-2.7/dogtail-run-headless-next319
-rwxr-xr-xbuild/scripts-2.7/sniff798
-rw-r--r--dogtail.spec213
-rwxr-xr-xdogtail/__init__.py16
-rw-r--r--dogtail/__init__.pycbin0 -> 894 bytes
-rwxr-xr-xdogtail/config.py219
-rw-r--r--dogtail/config.pycbin0 -> 7630 bytes
-rwxr-xr-xdogtail/distro.py362
-rwxr-xr-xdogtail/dump.py33
-rwxr-xr-xdogtail/errors.py28
-rwxr-xr-xdogtail/i18n.py281
-rw-r--r--dogtail/i18n.pycbin0 -> 8647 bytes
-rwxr-xr-xdogtail/logging.py218
-rw-r--r--dogtail/logging.pycbin0 -> 6901 bytes
-rwxr-xr-xdogtail/path.py113
-rw-r--r--dogtail/path.pycbin0 -> 4199 bytes
-rwxr-xr-xdogtail/predicate.py443
-rw-r--r--dogtail/predicate.pycbin0 -> 21364 bytes
-rwxr-xr-xdogtail/procedural.py455
-rwxr-xr-xdogtail/rawinput.py252
-rw-r--r--dogtail/rawinput.pycbin0 -> 8943 bytes
-rwxr-xr-xdogtail/sessions.py231
-rwxr-xr-xdogtail/tc.py230
-rwxr-xr-xdogtail/tree.py1318
-rw-r--r--dogtail/tree.pycbin0 -> 47327 bytes
-rwxr-xr-xdogtail/utils.py395
-rw-r--r--dogtail/utils.pycbin0 -> 16856 bytes
-rwxr-xr-xdogtail/version.py56
-rwxr-xr-xdogtail/wrapped.py33
-rw-r--r--examples/README65
-rw-r--r--examples/__init__.py0
-rwxr-xr-xexamples/build_meta_ide.sh7
-rwxr-xr-xexamples/configVariables.py155
-rwxr-xr-xexamples/eclipse_automation_test.py5897
-rwxr-xr-xexamples/iniparser.py44
l---------examples/results-run_eclipsetests.py.log1
-rwxr-xr-xexamples/run_eclipsetests.py136
-rwxr-xr-xexamples/settings-eclipse.ini181
-rw-r--r--icons/dogtail-head-48.pngbin0 -> 2759 bytes
-rw-r--r--icons/dogtail-head.svg595
-rw-r--r--icons/dogtail-tail-48.pngbin0 -> 2462 bytes
-rw-r--r--icons/dogtail-tail.svg336
-rw-r--r--scripts/dogtail-detect-session66
-rw-r--r--scripts/dogtail-logout28
-rw-r--r--scripts/dogtail-run-headless79
-rw-r--r--scripts/dogtail-run-headless-next319
-rwxr-xr-xsetup.py93
-rw-r--r--sniff/icons/button.xpm29
-rw-r--r--sniff/icons/checkbutton.xpm33
-rw-r--r--sniff/icons/checkmenuitem.xpm28
-rw-r--r--sniff/icons/colorselection.xpm35
-rw-r--r--sniff/icons/combo.xpm34
-rw-r--r--sniff/icons/dialog.xpm38
-rw-r--r--sniff/icons/image.xpm39
-rw-r--r--sniff/icons/label.xpm30
-rw-r--r--sniff/icons/menubar.xpm34
-rw-r--r--sniff/icons/menuitem.xpm26
-rw-r--r--sniff/icons/notebook.xpm33
-rw-r--r--sniff/icons/scrolledwindow.xpm28
-rw-r--r--sniff/icons/spinbutton.xpm33
-rw-r--r--sniff/icons/statusbar.xpm34
-rw-r--r--sniff/icons/table.xpm31
-rw-r--r--sniff/icons/text.xpm32
-rw-r--r--sniff/icons/toolbar.xpm33
-rw-r--r--sniff/icons/tree.xpm34
-rw-r--r--sniff/icons/treeitem.xpm35
-rw-r--r--sniff/icons/unknown.xpm33
-rw-r--r--sniff/icons/viewport.xpm34
-rw-r--r--sniff/icons/vscrollbar.xpm33
-rw-r--r--sniff/icons/vseparator.xpm31
-rw-r--r--sniff/icons/window.xpm38
-rwxr-xr-xsniff/sniff798
-rw-r--r--sniff/sniff.desktop14
-rw-r--r--sniff/sniff.glade676
-rw-r--r--sniff/sniff.ui494
-rw-r--r--tests/gtkdemotest.py56
-rw-r--r--tests/test_config.py66
-rw-r--r--tests/test_logging.py77
-rw-r--r--tests/test_node.py570
-rw-r--r--tests/test_predicate.py192
-rw-r--r--tests/test_procedural.py213
-rw-r--r--tests/test_utils.py64
128 files changed, 28816 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000000..3912109b5cd
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/HACKING b/HACKING
new file mode 100644
index 00000000000..5aacadf2835
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,11 @@
+= Notes on hacking on Dogtail =
+
+(this is a work-in-progress)
+
+
+== Formatting ==
+We use 4 spaces for indentation.
+
+== Python versions ==
+Currently the 0.8.x versions of dogtail target Python 2.7
+We might not be 3.x compatible yet.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 00000000000..8fb38c5e9fc
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,9 @@
+Installation on Red-Hat-like systems:
+ make rpm
+ rpm -Uvh ./dist/dogtail-*.noarch.rpm
+
+Installation on other systems:
+ ./setup.py install --prefix=/usr
+
+ (or /usr/local, $HOME/local, etc...)
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 00000000000..c8696454d57
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,146 @@
+# file GENERATED by distutils, do NOT edit
+COPYING
+HACKING
+INSTALL
+MANIFEST.in
+Makefile
+NEWS
+README
+dogtail.spec
+setup.py
+dogtail/__init__.py
+dogtail/config.py
+dogtail/distro.py
+dogtail/dump.py
+dogtail/errors.py
+dogtail/i18n.py
+dogtail/logging.py
+dogtail/path.py
+dogtail/predicate.py
+dogtail/procedural.py
+dogtail/rawinput.py
+dogtail/sessions.py
+dogtail/tc.py
+dogtail/tree.py
+dogtail/utils.py
+dogtail/version.py
+dogtail/wrapped.py
+examples/buildDirEclipse.py
+examples/commonFunctionEclipse.py
+examples/eclipseCommon.py
+examples/eclipseKeplerCAnsiProject.py
+examples/eclipseKeplerCGtkProject.py
+examples/eclipseKeplerCppAutoProject.py
+examples/eclipseKeplerPreferences.py
+examples/eclipseKeplerPreferences.txt
+examples/eclipseKeplerQEMU.py
+examples/eclipseKeplerSSHConnection.py
+examples/eclipseKeplerYoctoBSP.py
+examples/eclipseKeplerYoctoBSP1.py
+examples/eclipseYoctoBSP.txt
+examples/eclipseYoctoBSP1.txt
+examples/eclipseclick.py
+examples/gedit-test-utf8-procedural-api.py
+examples/gedit-test-utf8-tree-api.py
+examples/iniparser.py
+examples/logg-ubuntu-original.py
+examples/logg.py
+examples/logg.txt
+examples/runEclipse.py
+examples/startEclipse.py
+examples/testgedit.py
+examples/data/10b.png
+examples/data/10w.png
+examples/data/20b.png
+examples/data/20w.png
+examples/data/UTF-8-demo.txt
+examples/data/sample.cfg
+examples/hob/addLayer.py
+examples/hob/add_remove.py
+examples/hob/appstartup.py
+examples/hob/base.py
+examples/hob/baseImageSelection.py
+examples/hob/broughtInBy.py
+examples/hob/buildDir.py
+examples/hob/buildImage.py
+examples/hob/buildStopBuild.py
+examples/hob/buildToolchain.py
+examples/hob/changeDistro.py
+examples/hob/debBuild.py
+examples/hob/emgdDriver.py
+examples/hob/extraParameters.py
+examples/hob/extraParameters2.py
+examples/hob/filechooser-stress-test.py
+examples/hob/finish.py
+examples/hob/finishBuild.py
+examples/hob/i18n-test.py
+examples/hob/imagesShown.py
+examples/hob/ipkBuild.py
+examples/hob/multiplePackageBuild.py
+examples/hob/no-help-at-all.py
+examples/hob/noNative.py
+examples/hob/nonGPLv3Build.py
+examples/hob/nrThreads.py
+examples/hob/openLog.py
+examples/hob/packageSizeShown.py
+examples/hob/pyperclip.py
+examples/hob/recipeReloadBaseImage.py
+examples/hob/recipeReloadMachine.py
+examples/hob/recipesStop.py
+examples/hob/removeExtraParam.py
+examples/hob/rpmBuild.py
+examples/hob/runFullpass.py
+examples/hob/runWeekly.py
+examples/hob/startHob.py
+examples/hob/stopHob.py
+examples/hob/stopHob.txt
+examples/hob/taskReloadBaseImage.py
+examples/hob/test-events.py
+examples/hob/test.py
+examples/hob/testFinish.py
+examples/hob/testStopStart.py
+examples/test/eclipseKeplerYoctoBSP.py
+examples/test/eclipseYoctoBSP.txt
+icons/dogtail-head-48.png
+icons/dogtail-head.svg
+icons/dogtail-tail-48.png
+icons/dogtail-tail.svg
+scripts/dogtail-detect-session
+scripts/dogtail-logout
+scripts/dogtail-run-headless
+scripts/dogtail-run-headless-next
+sniff/sniff
+sniff/sniff.desktop
+sniff/sniff.glade
+sniff/sniff.ui
+sniff/icons/button.xpm
+sniff/icons/checkbutton.xpm
+sniff/icons/checkmenuitem.xpm
+sniff/icons/colorselection.xpm
+sniff/icons/combo.xpm
+sniff/icons/dialog.xpm
+sniff/icons/image.xpm
+sniff/icons/label.xpm
+sniff/icons/menubar.xpm
+sniff/icons/menuitem.xpm
+sniff/icons/notebook.xpm
+sniff/icons/scrolledwindow.xpm
+sniff/icons/spinbutton.xpm
+sniff/icons/statusbar.xpm
+sniff/icons/table.xpm
+sniff/icons/text.xpm
+sniff/icons/toolbar.xpm
+sniff/icons/tree.xpm
+sniff/icons/treeitem.xpm
+sniff/icons/unknown.xpm
+sniff/icons/viewport.xpm
+sniff/icons/vscrollbar.xpm
+sniff/icons/vseparator.xpm
+sniff/icons/window.xpm
+tests/gtkdemotest.py
+tests/test_config.py
+tests/test_logging.py
+tests/test_node.py
+tests/test_predicate.py
+tests/test_procedural.py
+tests/test_utils.py
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 00000000000..45afd7c085e
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,12 @@
+include COPYING
+include MANIFEST.in
+include Makefile
+include HACKING
+include INSTALL
+include NEWS
+include dogtail.spec
+recursive-include docs *
+recursive-include scripts *
+include sniff/sniff.glade sniff/sniff.desktop sniff/icons/*.xpm
+recursive-include examples *.py *.txt *.png *.cfg
+include icons/*.svg icons/*.png
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000000..f47423bc0eb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,52 @@
+# dogtail *development* Makefile
+
+all:
+ python setup.py build
+
+install:
+ python setup.py install --root=$(DESTDIR)
+
+clean:
+ rm -rf api_docs/
+ python setup.py clean
+ rm -f MANIFEST
+ rm -rf build dist
+
+ find . -name '*.pyc' -exec rm {} \;
+
+# Dollar signs must be escaped with dollar signs in variables.
+export camelCAPS='[a-z_][a-zA-Z0-9_]*$$'
+export StudlyCaps='[a-zA-Z_][a-zA-Z0-9_]*$$'
+
+check:
+ pylint --indent-string=" " --class-rgx=${StudlyCaps} --function-rgx=${camelCAPS} --method-rgx=${camelCAPS} --variable-rgx=${camelCAPS} --argument-rgx=${camelCaps} dogtail sniff/sniff examples/*.py recorder/dogtail-recorder scripts/*.py
+
+tarball:
+ python setup.py sdist
+
+rpm: tarball
+ # Build using the custom rpmrc in the rpms/ sub-dir
+ rpmbuild -tb dist/dogtail-*.tar.gz
+ # Move the source and binary RPMs to dist/
+ mv ~/rpmbuild/RPMS/noarch/* dist/
+
+srpm: rpm_prep
+ # Build using the custom rpmrc in the rpms/ sub-dir
+ rpmbuild --rcfile /usr/lib/rpm/rpmrc:/usr/lib/rpm/redhat/rpmrc:`pwd`/rpms/tmp.rpmrc -ts dist/dogtail-*.tar.gz
+ # Move the source and binary RPMs to dist/
+ mv rpms/SRPMS/* dist/
+ rm -rf rpms/
+
+apidocs: apidocs_html apidocs_pdf
+
+apidocs_html:
+ epydoc --html --config epydoc.conf
+
+apidocs_pdf:
+ epydoc --pdf --config epydoc.conf
+ mv api_docs/api.pdf api_docs/dogtail.pdf
+
+update_apidocs: apidocs
+ # Sadly, I'm still the only one who can update the API docs.
+ ssh zmc@fedorapeople.org rm -rf \~/public_html/dogtail/epydoc/*
+ scp api_docs/*.{html,css,png,pdf} zmc@fedorapeople.org:~/public_html/dogtail/epydoc/
diff --git a/NEWS b/NEWS
new file mode 100644
index 00000000000..8365b123609
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,597 @@
+dogtail 0.9.0
+=============
+Over the last year we've made a good number of fixes, added some new features
+and got overall stability to the point where we can make a new 'major minor'
+release. With 0.9 we're finally getting only a step away from what we'd like
+to get done with 1.0.
+
+
+ * Added a fix for situations when a previously crashed application
+ still registered with at-spi blocks search ('Broken' session problem)
+ Fixed for root.application(), root.applications() and for whenever
+ GeneralPredicate is used (.child()). Also done for Sniff. RHBZ 972257
+
+ * Added a support for gnome-continuous build-api (#36)
+
+ * Added a direct lambda support in the findChildren method, that allows
+ using non-predicate lambdas to create search properties. These can be
+ all pure dogtail Node properties (i.e. showing), not limiting to just
+ pyatspi ones as is when using Predicate.
+
+ * Added a GnomeShell 'helper' class into utils module allowing to work
+ with the new top-panel application menu.
+
+ * With GTK3.10+ Gdk changes, needed to tune the keyNameToKeyCode function
+ so that it provides valid keySym in as many cases possible - with both
+ pre-GTK3.10 as well as GTK3.11+ having the same behavior in typeText and
+ pressKey
+
+ * Sniff ui update: the info pane now stays down on resizing
+
+ * Got rid of some deprecated GObject calls
+
+ * Several updates to the dogtail-run-headless-next
+ - Any kind of session can now be used in any combination with both gdm/kdm
+ --session can be used to specify any xsession desktop file, 'kde' defaults
+ to 'kde-plasma' too keep pre-updated compatibility; default is now the previous user session
+ --dm - new param to pick either gdm (default) or kdm
+ --session-binary - new param to specify any in-session binary to grab script env from
+ - to be used with non gnome/kde session (lxde, ubuntu)
+ - do not confuse with session execs like gnome-session and starkde, that may
+ actually exit after session loadup; it should be everpresent (kwin, shell...)
+ - Added a --restart switch to force restart the previously running (often left-over) session before proceeding
+ - More logging of script execution (start/stop/PID), and some cleanups
+
+ * Trap GErrors in findAllDescendants and retry if it occurs: This fixes crashes in findChildren if elements are being added/removed quickly
+
+ * Unicode handling updates:
+ - Make sure safeDecode is not skipping any strings
+ - Don't crash when logging message contains unicode
+
+ * Error Handling for Attribute Error
+
+ * Fixed utils.run issue when having whitespace containing binary by
+ using shlex shell-like parsing split.
+
+ * Removed an always true if condition in i18n
+
+ * Log child description when clicking. The log will now print the element description when it will be clicked
+
+ * Trap TypeError in findAllDescendants: Sometimes pytaspi returns None as a Node if children are being removed too fast.
+ We should retry search in this case
+
+ * Extended the default TypingDelay from 0.075 to 0.1 seconds (helps working with certain high unicode charactes)
+
+ * Fixed an bug in sniff discovered by jmolet that made sniff crash when
+ run from /bin/sniff not /usr/bin/sniff
+
+ * Generate a better script method call for labels
+
+ * Fix for failing 'focus.application.node' seting
+
+ * Derive all classes from object explicitely
+
+ * Changed icons used in sniff that got moved to the gnome-icon-theme-legacy
+ recently for the ones from non-legacy package.
+
+ * Unified the 'isSth' vs. 'sth' properties for only 'sth' - added 'selected', but also introduced
+ 'isChecked' property for 0.7.x compatibility
+
+ * Added instructions on how to enable accessibility into utils.bailBecauseA11yIsDisabled()
+
+ * Added a 'retry' option to the tree.root.application()
+
+ * Fixed the concurrent creation of sniff_refresh lock from both tree and procedural
+
+ * Switched to use environ value to get user in config. os.getlogin breaks when run scheduled from a testing system with no terminal.
+
+ * Patched the mouse related rawinput methods to prevent using negative coordinates
+
+ * Updated and added more unittests
+
+dogtail 0.8.2
+=============
+Second update to the 0.8 series containing several fixes and improvements
+
+ * Added the dogtail-run-headless-next to replace the dogtail-run-headless in future.
+ -next, uses a diplay manager (kdm or gdm) for session management, should be used instead of older
+ headless on systemd systems already
+
+ * Unittests vastly improved and updated
+
+ * Fixed a missing reset of FocusWindow to None on application refocus
+
+ * Fixed the dogtail-logout to use gnome-shell instead of old gnome-panel
+
+dogtail 0.8.1
+=============
+A first update to the new GNOME3 / KDE4 compatible release containing several fixes:
+
+ * Sniff's autorefresh made togglable, to help avoid collisions with potential
+ running dogtail scripts. Sniff checks at startup whether some script is running
+ and sets up the autorefresh off if it is.
+
+ * Added a locking mechanism into utils that can be used to solve situations when
+ multiple dogtail processes/thread are running (applied in tree/procedural and sniff
+ already)
+
+ * Removed the deprecated dependency on CORBA
+
+ * Icons no longer use absolute pathing in sniff (thx roignac)
+
+ * Deprecated .has_key() operator replaced with 'in'.
+
+ * Removed .svg inside sniff's .desktop file
+
+dogtail 0.8.0
+=============
+Finally a big release updating dogtail after more than 2 years ! Dogtail
+is developed and maintained now again.
+
+Simply put, ported to be compatible with the new GNOME 3 and to work well in major
+GNOME 3 (GTK3) distributions.
+
+We'd like to make the 0.8.x version onward the GNOME 3 compatible branch usable
+for Fedora, RHEL7 and others, while keeping 0.7.x releases for fixes in older GNOME2
+systems.
+
+--- What was done ---
+
+Notably, Sniff's UI needed to ported completely to GTK3, yet there are
+also several places in dogtail 'itself' that needed to be rewritten to go ahead
+with the new technologies. Those were all the modules where the old pygtk was
+used for various reasons (rawinput, utils, tree...). Incompatibilites were also
+present due to the new version of pyatspi (notably with the doAction method).
+
+Release highlights:
+
+* A great number of fixes everywhere related to GTK/GNOME/At-Spi updates
+
+* The dogtail-recorder was dropped for now, but might return in RC or 0.8.1
+
+* Thanks to the qt-at-spi project, dogtail now works out-of-the-box for QT!
+
+* Headless supports KDE sessions
+
+* Headless working with full 3D-Accelerated GNOME session (no fallback anymore)
+
+* Highlight used in sniff completely re-written and made toggleable (of by default)
+
+* Makefile updated, 'make run' builds packages in homedir/rpmbuld now
+
+* Several examples fixed for updated gedit
+
+* Added tree.isChild() convenience method
+
+* Renamed doAction to doActionNamed due to conflict with doAction inside
+ pyatspi namespace
+
+* Delay made configurable in absoluteMotion and relativeMotion functions
+
+* Changed at-spi activation to use DConf instead of GConf
+
+* A bit of code clean-up and modernization (using @property now etc.)
+
+* More error checking added
+
+* A couple of unit tests for Node added
+
+* Node.findChildren() updated to be faster
+
+
+dogtail 0.7.0
+=============
+
+This release has too many changes to list. Here are some of the major ones:
+
+* Dogtail has been ported to pyatspi. This means:
+ - Faster performance.
+ - Errors will be more clear.
+ - No more segfaulting or myserious crashes.
+
+* There is now useful generated API documentation:
+ http://fedorapeople.org/~zmc/dogtail/epydoc/
+
+* Inline documentation has been greatly improved.
+
+* Headless execution support was completely rewritten.
+
+* Sniff was completely rewritten. It is now:
+ - Faster.
+ - More reliable.
+ - Self-updating via AT-SPI events.
+ - Slightly prettier.
+ - Able to tell you about certain states and relations.
+
+* Image Comparison (TCImage) was rewritten. No more ImageMagick!
+
+Note: tests/Node.py's TestNodeAttributes is failing due to GNOME bugs #498557
+ and #498563
+
+
+dogtail 0.6.1
+=============
+
+Features:
+
+ * dogtail-recorder has a Play button now.
+
+ * dogtail-recorder now records right-clicks. That it didn't previously was an
+ oversight. Note that there is some inconsistency when dealing with context
+ menus, as the events often get fired in the wrong order.
+
+ * New logging mechanism:
+
+ - Debug output is now written to a file. To disable this, set
+ dogtail.config.config.logDebugToFile to False.
+
+ - The procedural API defaults to not raising FocusErrors, and instead
+ issuing warnings via the debug logging mechanism (which prints to
+ standard out along with writing to a file). To re-enable FocusErrors, set
+ dogtail.config.config.fatalErrors to True.
+
+ * Brian Cameron submitted a patch to make dogtail.distro correctly detect that
+ it is running on Solaris.
+
+Bugfixes:
+
+ * dogtail-recorder no longer offers the broken Object-Oriented backend as a
+ choice. If it gets fixed, it will be offered again.
+
+ * dogtail-recorder can be properly exited by clicking on the window manager
+ close button. In fact, the Quit button was removed also.
+
+ * dogtail-recorder's syntax hilighting works again, thanks to a patch from
+ Dave Malcolm.
+
+ * Searching for nodes with parentheses in their names will no longer fail.
+
+ * The various pressKey() functions were blowing up on punctuation and
+ newlines. Fixed.
+
+ * Predicate subclasses in dogtail.predicate which were missing debugNames have
+ gotten them back.
+
+ * Warnings are no longer issued when dogtail encounters invalid Unicode.
+
+ * Where several of our example scripts were broken, Michal Babej noticed and
+ submitted patches fixing them.
+
+ * dogtail's tarball is 90% smaller! Some overly large test images were
+ replaced with very, very small ones that still do the job.
+
+
+dogtail 0.6.0
+=============
+
+Features:
+
+ * Keystroke recording support in dogtail-recorder:
+
+ - Normal keypresses (e.g. typing 'foo bar baz') will be recorded as
+ "type('foo bar baz')" in the procedural API. dogtail.tree.Node has a new
+ method, typeText(), which is called by dogtail.procedural.type().
+
+ - Nonprintable keypresses (e.g. typing Control-Shift-c) will be recorded as
+ "keyCombo('<Control><Shift>c')" in the procedural API. dogtail.tree.Node
+ has a new method, keyCombo(), which is called by
+ dogtail.procedural.keyCombo().
+
+ - The now-unnecessary 'Input Text' button has been removed.
+
+ * dogtail.tree.Node has new attributes and a new method:
+
+ - grabFocus(): Attempts to cause the node to grab the keyboard focus.
+
+ - focusable: Whether the node is able to have keyboard focus.
+
+ - focused: Whether the node currently has keyboard focus.
+
+ - checked: Whether the node is a checkbox that is currently checked.
+
+ * Dogtail now checks if accessibility is enabled before it does anything.
+ sniff and dogtail-recorder will prompt to enable it via a dialog, and
+ scripts themselves will just refuse to run.
+
+ * Dogtail no longer uses the IconLogger (in the notification area) by default.
+ Set dogtail.config.config.useIconLogger to True to reenable it.
+
+ * dogtail.utils.screenshot() was rewritten to use GDK instead of ImageMagick.
+
+Bugfixes:
+
+ * Dogtail is now much more robust in dealing with possibly-invalid Unicode
+ strings passed to it via AT-SPI. (GNOME bug #354515)
+
+ * Dogtail will not crash when it receives an SpiException caused by a CORBA
+ COMM_FAILURE, unless the source of that error is the application under test.
+
+ * While exceptions occuring inside callbacks used in dogtail-recorder will
+ still not properly be raised, they are now printed to standard out.
+
+ * SuSe support in dogtail.distro is fixed. (GNOME bug #353601)
+
+ * sniff and dogtail-recorder will now find their glade files, even if they're
+ installed into a nonstandard prefix. (GNOME bug #353719, #353731)
+
+ * dogtail-run-headless had a few bugs fixed.
+
+
+dogtail 0.5.2
+=============
+
+Features:
+
+ * A much-improved recorder:
+
+ - A procedural script writer, which is now default.
+
+ - An 'Input Text' feature, which allows you to set the text attribute
+ of any visible Node via the recorder. Coming soon is proper keystroke
+ recording.
+
+ - The script view now uses syntax hilighting where available. (GNOME bug
+ #345374)
+
+ * A completely-rewritten dogtail-run-headless, which now offers two session
+ types: minimal GNOME and metacity. The syntax has changed, so use
+ 'dogtail-run-headless --help' to get started. (GNOME bug #320548)
+
+ * All 'name' arguments in dogtail.tree and dogtail.procedural now accept
+ regular expressions.
+
+ * Support for AccessibleSelections, providing a clean way to select page
+ tabs and combo box items. (GNOME bug #336562)
+
+ * Support for building in a jhbuild environment. (GNOME bug #318535)
+
+ * Far more extensive unit tests for dogtail.tree.Node.
+
+Bugfixes:
+
+ * The source is now indented with spaces and not tabs! :) (GNOME bug #318833)
+
+ * Pyspi and dogtail no longer mysteriously stop working. (GNOME bug #321273)
+
+ * Sniff no longer crashes when an application being poked disappears. (GNOME
+ bug #318135)
+
+
+dogtail 0.5.1
+=============
+
+Features:
+
+ * An improved build system for RPMs.
+
+ * dogtail.procedural's FocusErrors are now more informative.
+
+Bugfixes:
+
+ * examples/recorder.py was broken; a bug in dogtail.predicate.makeCamel()
+ was breaking the predicates' makeScriptVariableName() calls. It's fixed.
+
+
+dogtail 0.5.0
+=============
+
+Features:
+
+ * Automatic runtime translation into the current locale using the application
+ under test's own translations. See
+ examples/gedit-test-utf8-procedural-api.py for an example.
+
+ * dogtail.distro.packageDb gained new methods: getFiles(), getMoFiles(), and
+ getDependencies().
+
+ * dogtail.distro.packageDb now has support for JHBuild environments.
+
+ * dogtail.distro has gained exceptions:
+
+ - PackageNotFoundError: raised when the specified package is not found.
+
+ - DistributionNotSupportedError: raised when support for the current
+ distribution is not currently implemented in dogtail.distro. This does
+ not mean that dogtail will not work at all; only that
+ distribution-specific functionality, like automatic runtime translation,
+ will not.
+
+ * dogtail.errors is a new module for very general exceptions, such as
+ DependencyNotFoundError, which is raised when the use of a feature that
+ depends on some missing piece of software is attempted.
+
+ * sniff will now cause the selected node to blink onscreen.
+
+ * dogtail.tree has a new Node subclass, Link. Link nodes have a roleName of
+ 'hyper link' and an action called 'jump'. Their main purpose is to allow
+ us to follow links in web pages.
+
+ * dogtail.tree.Node has new properties and methods:
+
+ - position: the onscreen position of the node, if it wraps an
+ atspi.Component.
+
+ - size: the onscreen size of the node, if it wraps an atspi.Component.
+
+ - blink(): causes the node to blink onscreen.
+
+ - grabFocus(): causes the node to grab the keyboard focus, if it wraps an
+ atspi.Component.
+
+ - rawClick(): synthesizes a raw mouse click on the node, if it wraps an
+ atspi.Component.
+
+ - rawType(): synthesizes raw keyboard events to type text into the node, if
+ it wraps an atspi.Component.
+
+ * dogtail.procedural has new helper methods:
+
+ - menu(): executes the 'menu' action on the focused widget.
+
+ - focus.frame(): a shortcut to focus.widget(roleName='frame' ...)
+
+ - focus.window(): a shortcut to focus.widget(roleName='window' ...)
+
+ * dogtail.procedural.click() can now synthesize raw mouse events to perform
+ "raw" clicks.
+
+ * dogtail.rawinput has gained new methods:
+
+ - pressKey(): synthesizes a raw keyboard event, pressing the key whose
+ name is specified.
+
+ - typeText(): types a specified string, one key at a time, using raw
+ keyboard events.
+
+ * dogtail.config was rewritten.
+
+ * dogtail.config.config has new parameters:
+
+ - ensureSensitivity: controls whether a NotSensitiveError is raised when
+ an attempt is made to execute an action belonging to a Node instance
+ that is not sensitive, or if a warning is simply printed instead (the
+ default).
+
+ - debugTranslation: Controls whether details of the autotranslation
+ process will be outputted to the debug logger.
+
+ * dogtail.config now creates its scratchDir, dataDir, and logDir as soon as
+ the values are set.
+
+ * dogtail.utils.screenshot(), by default, whether using a custom filename or
+ not, now appends a timestamp to the filename. That may be disabled by
+ passing 'timeStamp = False' to the method. For this reason, it also prints
+ the full path to the screenshot.
+
+ * dogtail.logging.TimeStamp.fileStamp()'s format has changed, to
+ filename_YYYYMMDD-hhmmss or just filename_YYYYMMDD, controlled by the new
+ optional argument 'addTime'.
+
+ * dogtail prints the full path to any logfiles it creates.
+
+ * dogtail-run-headless will now turn accessibility on before executing the
+ script under test, and also restore the old value after the the script has
+ finished executing (GNOME bug #320548).
+
+Bugfixes:
+
+ * sniff will correctly show the text of a non-editable text node again
+ (GNOME bug #321564).
+
+ * dogtail.tree.screenshot() was mostly rewritten, and no longer breaks when
+ given a custom filename.
+
+ * dogtail.procedural no longer causes PyDoc to fail miserably (or at all).
+
+ * dogtail will no longer warn about missing wnck bindings, because they're
+ not actually used anyway.
+
+ * dogtail.procedural.focus.dialog() no longer searches recursively (GNOME
+ bug #321624).
+
+ * dogtail.procedural.focus.widget() and its shortcut functions now properly
+ raise a FocusError if their search fails.
+
+ * dogtail.tc.TCString.compare() will now fail properly again, thanks to
+ Muktha Narayan (GNOME bug #321151).
+
+ * dogtail.tc.TCImage.compare() now works with the newest version of
+ ImageMagick. That version had changed its behavior, breaking the function.
+ (GNOME bug #321431).
+
+ * Attempting to instantiate a dogtail.tc.TCImage object while ImageMagick is
+ not installed will now give a comprehensible error message, thanks to the
+ new dogtail.errors.DependencyNotFoundError exception.
+
+ * Calling dogtail.utils.screenshot() while ImageMagick is not installed will
+ also raise a dogtail.errors.DependencyNotFoundError.
+
+ * Attempting to execute an action belonging to a Node instance that isn't
+ sensitive no longer fails by default, but prints a warning. This behavior
+ is controlled by the ensureSensitivity configuration parameter.
+
+ * dogtail-run-headless now correctly returns the exit code of the script
+ being executed (GNOME bug #320535).
+
+ * dogtail.distro's APT support implementation now only calls
+ apt_pkg.GetCache() once, improving performance significantly when multiple
+ calls to packageDb.get{Version,Dependencies}() are needed.
+
+ * Building RPM packages from the supplied dogtail.spec works again on distros
+ that are as old or older than RHEL4.
+
+ * Building RPM packages with 'make rpm' works again without root privileges.
+
+API breaks:
+
+ * Action execution via dogtail.tree.Node instances was forced to change
+ interfaces from the "node.open()" model to a "node.doAction('open')"
+ model. To retain backward compatibility with the application wrappers, the
+ "click" action is still available as "node.click()". The
+ dogtail.procedural interface was able to remain unchanged.
+
+ * dogtail.config was rewritten to be more robust, and its interface was
+ changed slightly. To port, First, remove any instantiations of the old
+ Config class, and then make the following parameter replacements:
+
+ - Config => config
+ - logdir => logDir
+ - scratch => scratchDir
+ - data => dataDir
+
+
+dogtail 0.4.3
+=============
+
+Features:
+ * Headless support has been added. Use dogtail-run-headless to spawn an X
+ server, a minimal "Nat/Jeff" GNOME session, and execute a given dogtail
+ script before exiting. The dogtail-detect-session script detects a running
+ GNOME session, but KDE support can be dropped in once KDE has accessibility
+ support.
+
+ * dogtail now uses a notification icon while it is running. Its tooltip is
+ equal to the last message relayed by the debug logger. Most of the code
+ was taken from jhbuild.
+
+ * Sniff, the AT-SPI browser that uses the dogtail libraries, has gained icons
+ for both applications and specific widgets (based on the role name, taken
+ from at-poke) to make browsing easier.
+
+ * Sniff also gained a proper dogtail icon.
+
+ * The procedural API has been given more convenience functions for 'focusing'
+ different types of widgets.
+
+ * The procedural API has also been given an openItem() method, which works
+ just like click(), for opening icons on nautilus' desktop.
+
+ * dogtail can now detect Gentoo and Conary distributions, thanks to Brent
+ Smith and Tim Gerla, respectively.
+
+ * The Node class in dogtail.tree now exports the 'role' attribute in addition
+ to the 'role name'.
+
+ * Wrappers for gcalctool and yelp were added, thanks to Dave Malcolm.
+
+ * A wrapper for gedit was added, thanks to Paolo Borelli.
+
+ * New examples for Evolution, gcalctool and the GTK file chooser were added,
+ thanks to Dave Malcolm.
+
+ * Dave Malcolm also enhanced the Evolution wrapper, adding support for
+ creating new meetings.
+
+ * A new example that generates fake documentation for a given application has
+ been added, thanks to Dave Malcolm.
+
+ * dogtail.spec has been added, thanks to Jeremy Katz, to improve the quality
+ of the RPM packages.
+
+Bugfixes:
+ * several examples had not been updated to reflect API changes before the
+ last release. This has been corrected.
+
+ * The epiphany wrapper now uses the package name 'epiphany-browser' on
+ Debian-based distributions to detect the package version, thanks to Andrew
+ Beresford.
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 00000000000..d683fe8fbff
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: dogtail
+Version: 0.9.0
+Summary: GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications.
+Home-page: http://dogtail.fedorahosted.org/
+Author: Zack Cerza <zcerza@redhat.com>,
+Ed Rousseau <rousseau@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>
+Vitezslav Humpa <vhumpa@redhat.com>
+Author-email: dogtail-list@gnome.org
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/README b/README
new file mode 100644
index 00000000000..1eb57cd6064
--- /dev/null
+++ b/README
@@ -0,0 +1,74 @@
+dogtail is a GUI test tool and automation framework written in Python. It uses Accessibility (a11y) technologies to communicate with desktop applications. dogtail scripts are written in Python and executed like any other Python program.
+
+
+News
+====
+
+See NEWS file.
+
+
+Installation
+============
+
+See INSTALL file.
+
+
+Dependencies
+============
+
+Python bindings for your distribution, e.g. python-apt or rpm-python
+
+PyGObject and GNOME-Python
+
+Applications to test, e.g. from the GNOME desktop:
+ http://gnome.org/
+
+Xvfb and xinit:
+ http://xorg.freedesktop.org/
+
+Using
+=====
+
+Currently GNOME and GTK+ applications are supported. Thanks to qt-at-spi
+KDE4 and QT applications are now available too.
+
+First, enable accessibility support in your GNOME session with:
+ gsettings set org.gnome.desktop.interface toolkit-accessibility true
+This only affects newly-started applications, so you may want to log out and
+log back in again.
+
+Then, look at some of the example scripts. Run them, tweak them, write your own.
+
+I suggest starting with gedit-test-utf8-procedural-api.py, as it's updated the
+most often.
+
+If you are using KDE instead, install the 'qt-at-spi' QT plugin and make sure
+you QT_ACCESSIBILITY set to 1 throughout your environment (you can put
+'export QT_ACCESSIBILITY=1' to your profile file). QT accessibility should
+be stable from QT 4.8.3 onward.
+
+Bugs
+====
+
+Please report any bugs at:
+ https://fedorahosted.org/dogtail/newticket
+
+
+Contact
+=======
+
+Website:
+ http://dogtail.fedorahosted.org/
+
+API Documentation:
+ http://fedorapeople.org/~vhumpa/dogtail/epydoc/
+
+IRC:
+ #dogtail on irc.freenode.net
+
+Mailing list for users:
+ dogtail-list@gnome.org
+
+Mailing list for developers:
+ dogtail-devel-list@gnome.org
+
diff --git a/build/lib.linux-x86_64-2.7/dogtail/__init__.py b/build/lib.linux-x86_64-2.7/dogtail/__init__.py
new file mode 100644
index 00000000000..76a4a13fcb5
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: UTF-8 -*-
+"""
+GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications.
+
+Authors: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vita Humpa <vhumpa@redhat.com>
+"""
+
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+Ed Rousseau <rousseau@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>,
+Vita Humpa <vhumpa@redhat.com>"""
+__version__ = "0.9.0"
+__copyright__ = "Copyright © 2005-2014 Red Hat, Inc."
+__license__ = "GPL"
+__all__ = ("config", "predicate",
+ "procedural", "tc", "tree", "utils", "errors")
diff --git a/build/lib.linux-x86_64-2.7/dogtail/config.py b/build/lib.linux-x86_64-2.7/dogtail/config.py
new file mode 100644
index 00000000000..82e197cf523
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/config.py
@@ -0,0 +1,219 @@
+"""
+The configuration module.
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>"
+
+import os
+import sys
+import locale
+
+
+def _userTmpDir(baseName):
+ # i.e. /tmp/dogtail-foo
+ return '-'.join(('/'.join(('/tmp', baseName)), os.environ['USER']))
+
+
+class _Config(object):
+
+ """
+ Contains configuration parameters for the dogtail run.
+
+ scratchDir(str):
+ Directory where things like screenshots are stored.
+
+ dataDir(str):
+ Directory where related data files are located.
+
+ logDir(str):
+ Directory where dogtail.tc.TC*-generated logs are stored.
+
+ scriptName(str) [Read-Only]:
+ The name of the script being run.
+
+ encoding(str)
+ The encoding for text, used by dogtail.tc.TCString .
+
+ actionDelay(float):
+ The delay after an action is executed.
+
+ typingDelay(float):
+ The delay after a character is typed on the keyboard.
+
+ runInterval(float):
+ The interval at which dogtail.utils.run() and dogtail.procedural.run()
+ check to see if the application has started up.
+
+ runTimeout(int):
+ The timeout after which dogtail.utils.run() and dogtail.procedural.run()
+ give up on looking for the newly-started application.
+
+ searchBackoffDuration (float):
+ Time in seconds for which to delay when a search fails.
+
+ searchWarningThreshold (int):
+ Number of retries before logging the individual attempts at a search.
+
+ searchCutoffCount (int):
+ Number of times to retry when a search fails.
+
+ defaultDelay (float):
+ Default time in seconds to sleep when delaying.
+
+ childrenLimit (int):
+ When there are a very large number of children of a node, only return
+ this many, starting with the first.
+
+ debugSearching (boolean):
+ Whether to write info on search backoff and retry to the debug log.
+
+ debugSleep (boolean):
+ Whether to log whenever we sleep to the debug log.
+
+ debugSearchPaths (boolean):
+ Whether we should write out debug info when running the SearchPath
+ routines.
+
+ absoluteNodePaths (boolean):
+ Whether we should identify nodes in the logs with long 'abcolute paths', or
+ merely with a short 'relative path'. FIXME: give examples
+
+ ensureSensitivity (boolean):
+ Should we check that ui nodes are sensitive (not 'greyed out') before
+ performing actions on them? If this is True (the default) it will raise
+ an exception if this happens. Can set to False as a workaround for apps
+ and toolkits that don't report sensitivity properly.
+
+ debugTranslation (boolean):
+ Whether we should write out debug information from the translation/i18n
+ subsystem.
+
+ blinkOnActions (boolean):
+ Whether we should blink a rectangle around a Node when an action is
+ performed on it.
+
+ fatalErrors (boolean):
+ Whether errors encountered in dogtail.procedural should be considered
+ fatal. If True, exceptions will be raised. If False, warnings will be
+ passed to the debug logger.
+
+ checkForA11y (boolean):
+ Whether to check if accessibility is enabled. If not, just assume it is
+ (default True).
+
+ logDebugToFile (boolean):
+ Whether to write debug output to a log file.
+
+ logDebugToStdOut (boolean):
+ Whether to print log output to console or not (default True).
+ """
+ @property
+ def scriptName(self):
+ return os.path.basename(sys.argv[0]).replace('.py', '')
+
+ @property
+ def encoding(self):
+ return locale.getpreferredencoding().lower()
+
+ defaults = {
+ # Storage
+ 'scratchDir': '/'.join((_userTmpDir('dogtail'), '')),
+ 'dataDir': '/'.join((_userTmpDir('dogtail'), 'data', '')),
+ 'logDir': '/'.join((_userTmpDir('dogtail'), 'logs', '')),
+ 'scriptName': scriptName.fget(None),
+ 'encoding': encoding.fget(None),
+ 'configFile': None,
+ 'baseFile': None,
+
+ # Timing and Limits
+ 'actionDelay': 1.0,
+ 'typingDelay': 0.1,
+ 'runInterval': 0.5,
+ 'runTimeout': 30,
+ 'searchBackoffDuration': 0.5,
+ 'searchWarningThreshold': 3,
+ 'searchCutoffCount': 20,
+ 'defaultDelay': 0.5,
+ 'childrenLimit': 100,
+
+ # Debug
+ 'debugSearching': False,
+ 'debugSleep': False,
+ 'debugSearchPaths': False,
+ 'logDebugToStdOut': True,
+ 'absoluteNodePaths': False,
+ 'ensureSensitivity': False,
+ 'debugTranslation': False,
+ 'blinkOnActions': False,
+ 'fatalErrors': False,
+ 'checkForA11y': True,
+
+ # Logging
+ 'logDebugToFile': True
+ }
+
+ options = {}
+
+ invalidValue = "__INVALID__"
+
+ def __init__(self):
+ _Config.__createDir(_Config.defaults['scratchDir'])
+ _Config.__createDir(_Config.defaults['logDir'])
+ _Config.__createDir(_Config.defaults['dataDir'])
+
+ def __setattr__(self, name, value):
+ if name not in config.defaults:
+ raise AttributeError(name + " is not a valid option.")
+
+ elif _Config.defaults[name] != value or \
+ _Config.options.get(name, _Config.invalidValue) != value:
+ if 'Dir' in name:
+ _Config.__createDir(value)
+ if value[-1] != os.path.sep:
+ value = value + os.path.sep
+ elif name == 'logDebugToFile':
+ import logging
+ logging.debugLogger = logging.Logger('debug', value)
+ _Config.options[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return _Config.options[name]
+ except KeyError:
+ try:
+ return _Config.defaults[name]
+ except KeyError:
+ raise AttributeError("%s is not a valid option." % name)
+
+ def __createDir(cls, dirName, perms=0o777):
+ """
+ Creates a directory (if it doesn't currently exist), creating any
+ parent directories it needs.
+
+ If perms is None, create with python's default permissions.
+ """
+ dirName = os.path.abspath(dirName)
+ # print "Checking for %s ..." % dirName,
+ if not os.path.isdir(dirName):
+ if perms:
+ umask = os.umask(0)
+ os.makedirs(dirName, perms)
+ os.umask(umask)
+ else:
+ # This is probably a dead code - no other functions call this without the permissions set
+ os.makedirs(dirName) # pragma: no cover
+ __createDir = classmethod(__createDir)
+
+ def load(self, dict):
+ """
+ Loads values from dict, preserving any options already set that are not overridden.
+ """
+ _Config.options.update(dict)
+
+ def reset(self):
+ """
+ Resets all settings to their defaults.
+ """
+ _Config.options = {}
+
+
+config = _Config()
diff --git a/build/lib.linux-x86_64-2.7/dogtail/distro.py b/build/lib.linux-x86_64-2.7/dogtail/distro.py
new file mode 100644
index 00000000000..6f70f194650
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/distro.py
@@ -0,0 +1,362 @@
+"""Handles differences between different distributions
+
+Authors: Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"
+
+import os
+import re
+from version import Version
+from logging import debugLogger as logger
+
+
+class DistributionNotSupportedError(Exception): # pragma: no cover
+
+ """
+ This distribution is not supported.
+ """
+ PATCH_MESSAGE = "Please send patches to dogtail-devel-list@gnome.org"
+
+ def __init__(self, distro):
+ self.distro = distro
+
+ def __str__(self):
+ return self.distro + ". " + DistributionNotSupportedError.PATCH_MESSAGE
+
+
+class PackageNotFoundError(Exception):
+
+ """
+ Error finding the requested package.
+ """
+ pass
+
+global packageDb
+global distro
+
+
+class PackageDb(object):
+
+ """
+ Class to abstract the details of whatever software package database is in
+ use (RPM, APT, etc)
+ """
+
+ def __init__(self):
+ self.prefix = '/usr'
+ self.localePrefixes = [self.prefix + '/share/locale']
+
+ def getVersion(self, packageName):
+ """
+ Method to get the version of an installed package as a Version
+ instance (or raise an exception if not found)
+
+ Note: does not know about distributions' internal revision numbers.
+ """
+ raise NotImplementedError
+
+ def getFiles(self, packageName):
+ """
+ Method to get a list of filenames owned by the package, or raise an
+ exception if not found.
+ """
+ raise NotImplementedError
+
+ def getMoFiles(self, locale=None):
+ """
+ Method to get a list of all .mo files on the system, optionally for a
+ specific locale.
+ """
+ moFiles = {}
+
+ def appendIfMoFile(moFiles, dirName, fNames):
+ import re
+ for fName in fNames:
+ if re.match('(.*)\\.mo', fName):
+ moFiles[dirName + '/' + fName] = None
+
+ for localePrefix in self.localePrefixes:
+ if locale:
+ localePrefix = localePrefix + '/' + locale
+ os.path.walk(localePrefix, appendIfMoFile, moFiles)
+
+ return moFiles.keys()
+
+ def getDependencies(self, packageName):
+ """
+ Method to get a list of unique package names that this package
+ is dependent on, or raise an exception if the package is not
+ found.
+ """
+ raise NotImplementedError
+
+
+class _RpmPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return Version.fromString(header["version"])
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return header["filenames"]
+ raise PackageNotFoundError(packageName)
+
+ def getDependencies(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+
+ # Get the list of requirements; these are
+ # sometimes package names, but can also be
+ # so-names of libraries, and invented virtual
+ # ids
+ for requirement in header[rpm.RPMTAG_REQUIRES]:
+ # Get the name of the package providing
+ # this requirement:
+ for depPackageHeader in ts.dbMatch("provides", requirement):
+ depName = depPackageHeader['name']
+ if depName != packageName:
+ # Add to the Hash with a dummy value
+ result[depName] = None
+ return result.keys()
+ raise PackageNotFoundError(packageName)
+
+
+class _AptPackageDb(PackageDb):
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ self.cache = None
+
+ def getVersion(self, packageName):
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ verString = re.match(
+ '.*Ver:\'(.*)-.*\' Section:', str(package.CurrentVer)).group(1)
+ return Version.fromString(verString)
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ files = []
+ list = os.popen('dpkg -L %s' % packageName).readlines()
+ if not list:
+ raise PackageNotFoundError(packageName)
+ else:
+ for line in list:
+ file = line.strip()
+ if file:
+ files.append(file)
+ return files
+
+ def getDependencies(self, packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ current = package.CurrentVer
+ if not current:
+ raise PackageNotFoundError(packageName)
+ depends = current.DependsList
+ list = depends['Depends']
+ for dependency in list:
+ name = dependency[0].TargetPkg.Name
+ # Add to the hash using a dummy value
+ result[name] = None
+ return result.keys()
+
+
+class _UbuntuAptPackageDb(_AptPackageDb):
+
+ def __init__(self):
+ _AptPackageDb.__init__(self)
+ self.localePrefixes.append(self.prefix + '/share/locale-langpack')
+
+
+class _PortagePackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ # the portage utilities are almost always going to be in
+ # /usr/lib/portage/pym
+ import sys
+ sys.path.append('/usr/lib/portage/pym')
+ import portage
+ # FIXME: this takes the first package returned in the list, in the
+ # case that there are slotted packages, and removes the leading
+ # category such as 'sys-apps'
+ gentooPackageName = portage.db["/"][
+ "vartree"].dbapi.match(packageName)[0].split('/')[1]
+ # this removes the distribution specific versioning returning only the
+ # upstream version
+ upstreamVersion = portage.pkgsplit(gentooPackageName)[1]
+ # print "Version of package is: " + upstreamVersion
+ return Version.fromString(upstreamVersion)
+
+
+class _ConaryPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ from conaryclient import ConaryClient
+ client = ConaryClient()
+ dbVersions = client.db.getTroveVersionList(packageName)
+ if not len(dbVersions):
+ raise PackageNotFoundError(packageName)
+ return dbVersions[0].trailingRevision().asString().split("-")[0]
+
+# getVersion not implemented because on Solaris multiple modules are installed
+# in single packages, so it is hard to tell what version number of a specific
+# module.
+
+
+class _SolarisPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+
+class JhBuildPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ prefixes = []
+ prefixes.append(os.environ['LD_LIBRARY_PATH'])
+ prefixes.append(os.environ['XDG_CONFIG_DIRS'])
+ prefixes.append(os.environ['PKG_CONFIG_PATH'])
+ self.prefix = os.path.commonprefix(prefixes)
+ self.localePrefixes.append(self.prefix + '/share/locale')
+
+ def getDependencies(self, packageName):
+ result = {}
+ lines = os.popen('jhbuild list ' + packageName).readlines()
+ for line in lines:
+ if line:
+ result[line.strip()] = None
+ return result.keys()
+
+
+class Distro(object):
+
+ """
+ Class representing a distribution.
+
+ Scripts may want to do arbitrary logic based on whichever distro is in use
+ (e.g. handling differences in names of packages, distribution-specific
+ patches, etc.)
+
+ We can either create methods in the Distro class to handle these, or we
+ can use constructs like isinstance(distro, Ubuntu) to handle this. We can
+ even create hierarchies of distro subclasses to handle this kind of thing
+ (could get messy fast though)
+ """
+
+
+class Fedora(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class RHEL(Fedora): # pragma: no cover
+ pass
+
+
+class Debian(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _AptPackageDb()
+
+
+class Ubuntu(Debian):
+
+ def __init__(self):
+ self.packageDb = _UbuntuAptPackageDb()
+
+
+class Suse(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class Gentoo(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _PortagePackageDb()
+
+
+class Conary(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _ConaryPackageDb()
+
+
+class Solaris(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _SolarisPackageDb()
+
+
+class JHBuild(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = JhBuildPackageDb()
+
+
+def detectDistro():
+ logger.log("Detecting distribution:", newline=False)
+
+ if os.environ.get("CERTIFIED_GNOMIE", "no") == "yes":
+ distro = JHBuild() # pragma: no cover
+ elif os.path.exists("/etc/SuSE-release"):
+ distro = Suse() # pragma: no cover
+ elif os.path.exists("/etc/fedora-release"):
+ distro = Fedora() # pragma: no cover
+ elif os.path.exists("/etc/redhat-release"):
+ distro = RHEL() # pragma: no cover
+ elif os.path.exists("/usr/share/doc/ubuntu-minimal"):
+ distro = Ubuntu()
+ elif os.path.exists("/etc/debian_version"): # pragma: no cover
+ distro = Debian() # pragma: no cover
+ elif os.path.exists("/etc/gentoo-release"): # pragma: no cover
+ distro = Gentoo() # pragma: no cover
+ elif os.path.exists("/etc/slackware-version"): # pragma: no cover
+ raise DistributionNotSupportedError("Slackware") # pragma: no cover
+ elif os.path.exists("/var/lib/conarydb/conarydb"): # pragma: no cover
+ distro = Conary() # pragma: no cover
+ elif os.path.exists("/etc/release") and \
+ re.match(".*Solaris", open("/etc/release").readline()): # pragma: no cover
+ distro = Solaris() # pragma: no cover
+ else:
+ raise DistributionNotSupportedError("Unknown") # pragma: no cover
+ logger.log(distro.__class__.__name__)
+ return distro
+
+distro = detectDistro()
+packageDb = distro.packageDb
diff --git a/build/lib.linux-x86_64-2.7/dogtail/dump.py b/build/lib.linux-x86_64-2.7/dogtail/dump.py
new file mode 100644
index 00000000000..3756820a510
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/dump.py
@@ -0,0 +1,33 @@
+"""Utility functions for 'dumping' trees of Node objects.
+
+Author: Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from __builtin__ import file
+
+spacer = ' '
+
+
+def plain(node, fileName=None):
+ """
+ Plain-text dump. The hierarchy is represented through indentation.
+ """
+ def crawl(node, depth):
+ dump(node, depth)
+ for action in node.actions.values():
+ dump(action, depth + 1)
+ for child in node.children:
+ crawl(child, depth + 1)
+
+ def dumpFile(item, depth):
+ _file.write(spacer * depth + str(item) + '\n')
+
+ def dumpStdOut(item, depth):
+ print(spacer * depth + str(item))
+ if fileName:
+ dump = dumpFile
+ _file = file(fileName, 'w')
+ else:
+ dump = dumpStdOut
+
+ crawl(node, 0)
diff --git a/build/lib.linux-x86_64-2.7/dogtail/errors.py b/build/lib.linux-x86_64-2.7/dogtail/errors.py
new file mode 100644
index 00000000000..648af9d598e
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/errors.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+General exceptions; not overly module-specific
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+from logging import debugLogger as logger
+
+import inspect
+
+
+def warn(message, caller=True):
+ """
+ Generate a warning, and pass it to the debug logger.
+ """
+ frameRec = inspect.stack()[-1]
+ message = "Warning: %s:%s: %s" % (frameRec[1], frameRec[2], message)
+ if caller and frameRec[1] != '<stdin>' and frameRec[1] != '<string>':
+ message = message + ':\n ' + frameRec[4][0]
+ del frameRec
+ logger.log(message)
+
+
+class DependencyNotFoundError(Exception):
+
+ """
+ A dependency was not found.
+ """
+ pass
diff --git a/build/lib.linux-x86_64-2.7/dogtail/i18n.py b/build/lib.linux-x86_64-2.7/dogtail/i18n.py
new file mode 100644
index 00000000000..8117f8d82c6
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/i18n.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+"""
+Internationalization facilities
+
+Authors: David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+
+import config
+
+import os
+import re
+import gettext
+
+from logging import debugLogger as logger
+from __builtin__ import unicode
+
+
+def safeDecode(string):
+ try:
+ string = string.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ string = string.encode('utf-8', 'replace')
+ return string
+
+
+def safeEncode(string):
+ pass
+
+
+"""
+Singleton list of TranslationDb instances, to be initialized by the script with
+whatever translation databases it wants.
+"""
+translationDbs = []
+
+
+class TranslationDb(object):
+
+ """
+ Abstract base class representing a database of translations
+ """
+
+ def getTranslationsOf(self, srcName):
+ """
+ Pure virtual method to look up the translation of a string.
+ Returns a list of candidate strings (the translation), empty if not found.
+
+ Note that a source string can map to multiple translated strings. For
+ example, in the French translation of Evolution, the string "Forward" can
+ translate to both
+ (i) "Faire suivre" for forwarding an email, and
+ (ii) "Suivant" for the next page in a wizard.
+ """
+ raise NotImplementedError
+
+
+class GettextTranslationDb(TranslationDb):
+
+ """
+ Implementation of TranslationDb which leverages gettext, using a single
+ translation mo-file.
+ """
+
+ def __init__(self, moFile):
+ self.__moFile = moFile
+ self.__gnutranslations = gettext.GNUTranslations(open(moFile))
+
+ def getTranslationsOf(self, srcName):
+ srcName = safeDecode(srcName)
+ # print "searching for translations of %s"%srcName
+ # Use a dict to get uniqueness:
+ results = {}
+ result = self.__gnutranslations.ugettext(srcName)
+ if result != srcName:
+ results[result] = None
+
+ # Hack alert:
+ #
+ # Note that typical UI definition in GTK etc contains strings with
+ # underscores to denote accelerators.
+ # For example, the stock GTK "Add" item has text "_Add" which e.g.
+ # translates to "A_jouter" in French
+ #
+ # Since these underscores have been stripped out before we see these strings,
+ # we are looking for a translation of "Add" into "Ajouter" in this case, so
+ # we need to fake it, by looking up the string multiple times, with underscores
+ # inserted in all possible positions, stripping underscores out of the result.
+ # Ugly, but it works.
+
+ for index in range(len(srcName)):
+ candidate = srcName[:index] + "_" + srcName[index:]
+ result = self.__gnutranslations.ugettext(candidate)
+ if result != candidate:
+ # Strip out the underscore, and add to the result:
+ results[result.replace('_', '')] = True
+
+ return results.keys()
+
+
+def translate(srcString):
+ """
+ Look up srcString in the various translation databases (if any), returning
+ a list of all matches found (potentially the empty list)
+ """
+ # Use a dict to get uniqueness:
+ results = {}
+ # Try to translate the string:
+ for translationDb in translationDbs:
+ for result in translationDb.getTranslationsOf(srcString):
+ result = safeDecode(result)
+ results[result] = True
+
+ # No translations found:
+ if len(results) == 0:
+ if config.config.debugTranslation:
+ logger.log('Translation not found for "%s"' % srcString)
+ return results.keys()
+
+
+class TranslatableString(object):
+
+ """
+ Class representing a string that we want to match strings against, handling
+ translation for us, by looking it up once at construction time.
+ """
+
+ def __init__(self, untranslatedString):
+ """
+ Constructor looks up the string in all of the translation databases, storing
+ the various translations it finds.
+ """
+ untranslatedString = safeDecode(untranslatedString)
+ self.untranslatedString = untranslatedString
+ self.translatedStrings = translate(untranslatedString)
+
+ def matchedBy(self, string):
+ """
+ Compare the test string against either the translation of the original
+ string (or simply the original string, if no translation was found).
+ """
+ # print "comparing %s against %s"%(string, self)
+ def stringsMatch(inS, outS):
+ """
+ Compares a regular expression to a string
+
+ inS: the regular expression (or normal string)
+ outS: the normal string to be compared against
+ """
+ inString = str(inS)
+ outString = outS
+ if inString == outString:
+ return True
+ inString = inString + '$'
+ inString = safeDecode(inString)
+ outString = safeDecode(outString)
+ if inString[0] == '*':
+ inString = "\\" + inString
+ # Escape all parentheses, since grouping will never be needed here
+ inString = re.sub('([\(\)])', r'\\\1', inString)
+ match = re.match(inString, outString)
+ matched = match is not None
+ return matched
+
+ matched = False
+ # the 'ts' variable keeps track of whether we're working with
+ # translated strings. it's only used for debugging purposes.
+ #ts = 0
+ # print string, str(self)
+ for translatedString in self.translatedStrings:
+ #ts = ts + 1
+ matched = stringsMatch(translatedString, string)
+ if not matched:
+ matched = translatedString == string
+ if matched:
+ return matched
+ # ts=0
+ return stringsMatch(self.untranslatedString, string)
+
+ def __str__(self):
+ """
+ Provide a meaningful debug version of the string (and the translation in
+ use)
+ """
+ if len(self.translatedStrings) > 0:
+ # build an output string, with commas in the correct places
+ translations = ""
+ for tString in self.translatedStrings:
+ translations += u'"%s", ' % safeDecode(tString)
+ result = u'"%s" (%s)' % (
+ safeDecode(self.untranslatedString), translations)
+ return safeDecode(result)
+ else:
+ return '"%s"' % (self.untranslatedString)
+
+
+def isMoFile(filename, language=''):
+ """
+ Does the given filename look like a gettext mo file?
+
+ Optionally: Does the file also contain translations for a certain language,
+ for example 'ja'?
+ """
+ if re.match('(.*)\\.mo$', filename):
+ if not language:
+ return True
+ elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' %
+ language, filename):
+ return True
+ else:
+ return False
+ else:
+ return False
+
+
+def loadAllTranslationsForLanguage(language):
+ import distro
+ for moFile in distro.packageDb.getMoFiles(language):
+ translationDbs.append(GettextTranslationDb(moFile))
+
+
+def getMoFilesForPackage(packageName, language='', getDependencies=True):
+ """
+ Look up the named package and find all gettext mo files within it and its
+ dependencies. It is possible to restrict the results to those of a certain
+ language, for example 'ja'.
+ """
+ import distro
+
+ result = []
+ for filename in distro.packageDb.getFiles(packageName):
+ if isMoFile(filename, language):
+ result.append(filename)
+
+ if getDependencies:
+ # Recurse:
+ for dep in distro.packageDb.getDependencies(packageName):
+ # We pass False to the inner call because getDependencies has already
+ # walked the full tree
+ result.extend(getMoFilesForPackage(dep, language, False))
+
+ return result
+
+
+def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
+ """
+ Helper function which appends all of the gettext translation mo-files used by
+ the package (and its dependencies) to the translation database list.
+ """
+ # Keep a list of mo-files that are already in use to avoid duplicates.
+ moFiles = {}
+
+ def load(packageName, language='', getDependencies=True):
+ for moFile in getMoFilesForPackage(packageName, language, getDependencies):
+ # Searching the popt mo-files for translations makes gettext bail out,
+ # so we ignore them here. This is
+ # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 .
+ if not('popt.mo' in moFile or moFile in moFiles):
+ try:
+ translationDbs.append(GettextTranslationDb(moFile))
+ moFiles[moFile] = None
+ except (AttributeError, IndexError):
+ if config.config.debugTranslation:
+ #import traceback
+ # logger.log(traceback.format_exc())
+ logger.log(
+ "Warning: Failed to load mo-file for translation: " + moFile)
+
+ # Hack alert:
+ #
+ # The following special-case is necessary for Ubuntu, since their
+ # translations are shipped in a single huge package. The downside to
+ # this special case, aside from the simple fact that there is one,
+ # is that it makes automatic translations much slower.
+
+ import distro
+ language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2]
+ if isinstance(distro.distro, distro.Ubuntu):
+ load('language-pack-gnome-%s' % language, language)
+ load(packageName, language, getDependencies)
diff --git a/build/lib.linux-x86_64-2.7/dogtail/logging.py b/build/lib.linux-x86_64-2.7/dogtail/logging.py
new file mode 100644
index 00000000000..7e73f163b83
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/logging.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+"""
+Logging facilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+import os
+import sys
+import time
+from config import config
+import codecs
+
+# Timestamp class for file logs
+
+
+class TimeStamp(object):
+
+ """
+ Generates timestamps tempfiles and log entries
+ """
+
+ def __init__(self):
+ self.now = "0"
+ self.timetup = time.localtime()
+
+ def zeroPad(self, int, width=2):
+ """
+ Pads an integer 'int' with zeroes, up to width 'width'.
+
+ Returns a string.
+
+ It will not truncate. If you call zeroPad(100, 2), '100' will be returned.
+ """
+ if int < 10 ** width:
+ return ("0" * (width - len(str(int)))) + str(int)
+ else:
+ return str(int)
+
+ # file stamper
+ def fileStamp(self, filename, addTime=True):
+ """
+ Generates a filename stamp in the format of filename_YYYYMMDD-hhmmss.
+ A format of filename_YYYYMMDD can be used instead by specifying addTime = False.
+ """
+ self.now = filename.strip() + "_"
+ self.timetup = time.localtime()
+
+ # Should produce rel-eng style filestamps
+ # format it all pretty by chopping the tuple
+ fieldCount = 3
+ if addTime:
+ fieldCount = fieldCount + 3
+ for i in range(fieldCount):
+ if i == 3:
+ self.now = self.now + '-'
+ self.now = self.now + self.zeroPad(self.timetup[i])
+ return self.now
+
+ # Log entry stamper
+ def entryStamp(self):
+ """
+ Generates a logfile entry stamp of YYYY.MM.DD HH:MM:SS
+ """
+ self.timetup = time.localtime()
+
+ # This will return a log entry formatted string in YYYY.MM.DD HH:MM:SS
+ for i in range(6):
+ # put in the year
+ if i == 0:
+ self.now = str(self.timetup[i])
+ # Format Month and Day
+ elif i == 1 or i == 2:
+ self.now = self.now + "." + self.zeroPad(self.timetup[i])
+ else:
+ # make the " " between Day and Hour and put in the hour
+ if i == 3:
+ self.now = self.now + " " + self.zeroPad(self.timetup[i])
+ # Otherwise Use the ":" divider
+ else:
+ self.now = self.now + ":" + self.zeroPad(self.timetup[i])
+ return self.now
+
+
+class Logger(object):
+
+ """
+ Writes entries to standard out.
+ """
+ stamper = TimeStamp()
+
+ def __init__(self, logName, file=False, stdOut=True):
+ """
+ name: the name of the log
+ file: The file object to log to.
+ stdOut: Whether to log to standard out.
+ """
+ self.logName = logName
+ self.stdOut = stdOut
+ self.file = file # Handle to the logfile
+ if not self.file:
+ return
+
+ scriptName = config.scriptName
+ if not scriptName:
+ scriptName = 'log'
+ self.fileName = scriptName
+
+ # check to see if we can write to the logDir
+ if os.path.isdir(config.logDir):
+ self.findUniqueName()
+ else:
+ # If path doesn't exist, raise an exception
+ raise IOError(
+ "Log path %s does not exist or is not a directory" % config.logDir)
+
+ def findUniqueName(self):
+ # generate a logfile name and check if it already exists
+ self.fileName = config.logDir + self.stamper.fileStamp(self.fileName) \
+ + '_' + self.logName
+ i = 0
+ while os.path.exists(self.fileName):
+ # Append the pathname
+ if i == 0:
+ self.fileName = self.fileName + "." + str(i)
+ else:
+ logsplit = self.fileName.split(".")
+ logsplit[-1] = str(i)
+ self.fileName = ".".join(logsplit)
+ i += 1
+
+ def createFile(self):
+ # Try to create the file and write the header info
+ print("Creating logfile at %s ..." % self.fileName)
+ self.file = codecs.open(self.fileName, mode='wb', encoding=
+ 'utf-8')
+ self.file.write("##### " + os.path.basename(self.fileName) + '\n')
+ self.file.flush()
+
+ def log(self, message, newline=True, force=False):
+ """
+ Hook used for logging messages. Might eventually be a virtual
+ function, but nice and simple for now.
+
+ If force is True, log to a file irrespective of config.logDebugToFile.
+ """
+ try:
+ message = message.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ pass
+
+
+ # Try to open and write the result to the log file.
+ if isinstance(self.file, bool) and (force or config.logDebugToFile):
+ self.createFile()
+
+ if force or config.logDebugToFile:
+ if newline:
+ self.file.write(message + '\n')
+ else:
+ self.file.write(message + ' ')
+ self.file.flush()
+
+ if self.stdOut and config.logDebugToStdOut:
+ if newline:
+ print(message)
+ else:
+ print(message)
+
+
+class ResultsLogger(Logger):
+
+ """
+ Writes entries into the Dogtail log
+ """
+
+ def __init__(self, stdOut=True):
+ Logger.__init__(self, 'results', file=True, stdOut=stdOut)
+
+ # Writes the result of a test case comparison to the log
+ def log(self, entry):
+ """
+ Writes the log entry. Requires a 1 {key: value} pair dict for an argument or else it will throw an exception.
+ """
+ # We require a 1 key: value dict
+ # Strip all leading and trailing witespace from entry dict and convert
+ # to string for writing
+
+ if len(entry) == 1:
+ key = entry.keys()
+ value = entry.values()
+ key = key[0]
+ value = value[0]
+ entry = str(key) + ": " + str(value)
+ else:
+ raise ValueError(entry)
+ print(
+ "Method argument requires a 1 {key: value} dict. Supplied argument not one {key: value}")
+
+ Logger.log(self, self.stamper.entryStamp() + " " + entry,
+ force=True)
+
+debugLogger = Logger('debug', config.logDebugToFile)
+
+import traceback
+
+
+def exceptionHook(exc, value, tb): # pragma: no cover
+ tbStringList = traceback.format_exception(exc, value, tb)
+ tbString = ''.join(tbStringList)
+ debugLogger.log(tbString)
+ sys.exc_clear()
+
+sys.excepthook = exceptionHook
diff --git a/build/lib.linux-x86_64-2.7/dogtail/path.py b/build/lib.linux-x86_64-2.7/dogtail/path.py
new file mode 100644
index 00000000000..5742cfb03e6
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/path.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+Author: David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """David Malcolm <dmalcolm@redhat.com>"""
+
+
+class SearchPath(object):
+
+ """
+ Class used by the recording framework (and for more verbose script
+ logging) for identifying nodes in a persistent way, independent of the
+ style of script being written.
+
+ Implemented as a list of (predicate, isRecursive) pairs, giving the
+ 'best' way to find the Accessible wrapped by a Node, starting at the
+ root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem, except
+ that some of searches may be recursive, rather than just searching
+ direct children.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+
+ def __init__(self):
+ self.__list = []
+
+ def __str__(self):
+ result = "{"
+ for (predicate, isRecursive) in self.__list:
+ result += "/(%s,%s)" % (
+ predicate.describeSearchResult(), isRecursive)
+ return result + "}"
+
+ # We need equality to work so that dicts of these work:
+ def __eq__(self, other):
+ # print "eq: self:%s"%self
+ # print " other:%s"%other
+ if len(self.__list) != len(other.__list):
+ # print "nonequal length"
+ return False
+ else:
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ return False
+ # print True
+ return True
+
+ def append(self, predicate, isRecursive):
+ assert predicate
+ self.__list.append((predicate, isRecursive))
+
+ def __iter__(self):
+ return iter(self.__list)
+
+ def length(self):
+ return len(self.__list)
+
+ def makeScriptMethodCall(self):
+ """
+ Used by the recording system.
+
+ Generate the Python source code that will carry out this search.
+ """
+ result = ""
+ for (predicate, isRecursive) in self.__list:
+ # print predicate
+ # print self.generateVariableName(predicate)
+ result += "." + predicate.makeScriptMethodCall(isRecursive)
+ return result
+
+ def getRelativePath(self, other):
+ """
+ Given another SearchPath instance, if the other is 'below' this
+ one, return a SearchPath that describes how to reach it relative
+ to this one (a copy of the second part of the list). Otherwise
+ return None.
+ """
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ break
+ if i > 0:
+ # Slice from this point to the end:
+ result = SearchPath()
+ result.__list = other.__list[i + 1:]
+
+ if False:
+ print("....................")
+ print("from %s" % self)
+ print("to %s" % other)
+ print("i=%s" % i)
+ print("relative path %s" % result)
+ print("....................")
+
+ return result
+ else:
+ return None
+
+ def getPrefix(self, n):
+ """
+ Get the first n components of this instance as a new instance
+ """
+ result = SearchPath()
+ for i in range(n):
+ result.__list.append(self.__list[i])
+ return result
+
+ def getPredicate(self, i):
+ (predicate, isRecursive) = self.__list[i]
+ return predicate
diff --git a/build/lib.linux-x86_64-2.7/dogtail/predicate.py b/build/lib.linux-x86_64-2.7/dogtail/predicate.py
new file mode 100644
index 00000000000..aa100e1f054
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/predicate.py
@@ -0,0 +1,443 @@
+"""Predicates that can be used when searching for nodes.
+
+Author: David Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'David Malcolm <dmalcolm@redhat.com>'
+
+from i18n import TranslatableString
+from gi.repository import GLib
+from time import sleep
+from logging import debugLogger as logger
+from config import config
+
+def stringMatches(scriptName, reportedName):
+ assert isinstance(scriptName, TranslatableString)
+
+ return scriptName.matchedBy(reportedName)
+
+
+def makeScriptRecursiveArgument(isRecursive, defaultValue):
+ if isRecursive == defaultValue:
+ return ""
+ else:
+ return ", recursive=%s" % isRecursive
+
+
+def makeCamel(string):
+ """
+ Convert string to camelCaps
+ """
+ string = str(string)
+ # FIXME: this function is probably really fragile, lots of difficult cases
+ # here
+
+ # Sanitize string, replacing bad characters with spaces:
+ for char in ":;!@#$%^&*()-+=_~`\\/?|[]{}<>,.\t\n\r\"'":
+ string = string.replace(char, " ")
+ words = string.strip().split(" ")
+ for word in words:
+ word.strip
+ result = ""
+ firstWord = True
+ for word in words:
+ lowercaseWord = word.lower()
+ if firstWord:
+ result += lowercaseWord
+ firstWord = False
+ else:
+ result += lowercaseWord.capitalize()
+ return result
+
+
+class Predicate(object):
+
+ """Abstract base class representing a predicate function on nodes.
+
+ It's more than just a function in that it has data and can describe itself"""
+
+ def satisfiedByNode(self, node):
+ """Pure virtual method returning a boolean if the predicate is satisfied by the node"""
+ raise NotImplementedError
+
+ def describeSearchResult(self, node):
+ raise NotImplementedError
+
+ def makeScriptMethodCall(self, isRecursive):
+ """
+ Method to generate a string containing a (hopefully) readable search
+ method call on a node (to be used when generating Python source code in
+ the event recorder)
+ """
+ raise NotImplementedError
+
+ def makeScriptVariableName(self):
+ """
+ Method to generate a string containing a (hopefully) readable name
+ for a Node instance variable that would be the result of a search on
+ this predicate (to be used when generating Python source code in the
+ event recorder).
+ """
+ raise NotImplementedError
+
+ def __eq__(self, other):
+ """
+ Predicates are considered equal if they are of the same subclass and
+ have the same data
+ """
+ # print "predeq: self:%s"%self
+ # print " other:%s"%other
+ # print "predeq: selfdict:%s"%self.__dict__
+ # print " otherdict:%s"%other.__dict__
+
+ if type(self) != type(other):
+ return False
+ else:
+ return self.__dict__ == other.__dict__
+
+
+class IsAnApplicationNamed(Predicate):
+
+ """Search subclass that looks for an application by name"""
+
+ def __init__(self, appName):
+ self.appName = TranslatableString(appName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ try:
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ try:
+ sleep(config.defaults['searchWarningThreshold'])
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError:
+ logger.log("Dogtail: warning: application may be hanging")
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s application' % self.appName
+
+ def makeScriptMethodCall(self, isRecursive):
+ # ignores the isRecursive parameter
+ return "application(%s)" % self.appName
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.appName) + "App"
+
+
+class GenericPredicate(Predicate):
+
+ """SubtreePredicate subclass that takes various optional search fields"""
+
+ def __init__(self, name=None, roleName=None, description=None, label=None, debugName=None):
+ if name:
+ self.name = TranslatableString(name)
+ else:
+ self.name = None
+ self.roleName = roleName
+ self.description = description
+ if label:
+ self.label = TranslatableString(label)
+ else:
+ self.label = None
+
+ if debugName:
+ self.debugName = debugName
+ else:
+ if label:
+ self.debugName = "labelled '%s'" % self.label
+ else:
+ self.debugName = "child with"
+ if name:
+ self.debugName += " name=%s" % self.name
+ if roleName:
+ self.debugName += " roleName='%s'" % roleName
+ if description:
+ self.debugName += " description='%s'" % description
+ assert self.debugName
+
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # labelled nodes are handled specially:
+ if self.label:
+ # this reverses the search; we're looking for a node with LABELLED_BY
+ # and then checking the label, rather than looking for a label and
+ # then returning whatever LABEL_FOR targets
+ if node.labeller:
+ return stringMatches(self.label, node.labeller.name)
+ else:
+ return False
+ else:
+ # Ensure the node matches any criteria that were set:
+ try:
+ if self.name:
+ if not stringMatches(self.name, node.name):
+ return False
+ if self.roleName:
+ if self.roleName != node.roleName:
+ return False
+ if self.description:
+ if self.description != node.description:
+ return False
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ raise e
+ return True
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return self.debugName
+
+ def makeScriptMethodCall(self, isRecursive):
+ if self.label:
+ args = "label=%s" % self.label
+ else:
+ args = ""
+ if self.name:
+ print(self.name)
+ args += " name=%s" % self.name
+ if self.roleName:
+ args += " roleName='%s'" % self.roleName
+ if self.description:
+ args += " description='%s'" % self.description
+ return "child(%s%s)" % (args, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ if self.label:
+ return makeCamel(self.label) + "Node"
+ else:
+ if self.name:
+ return makeCamel(self.name) + "Node"
+ if self.roleName:
+ return makeCamel(self.roleName) + "Node"
+ if self.description:
+ return makeCamel(self.description) + "Node"
+
+
+class IsNamed(Predicate):
+
+ """Predicate subclass that looks simply by name"""
+
+ def __init__(self, name):
+ self.name = TranslatableString(name)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return stringMatches(self.name, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "named %s" % self.name
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(name=%s%s)" % (self.name, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.name) + "Node"
+
+
+class IsAWindowNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level window by name"""
+
+ def __init__(self, windowName):
+ self.windowName = TranslatableString(windowName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'frame' and stringMatches(self.windowName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "%s window" % self.windowName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "window(%s%s)" % (self.windowName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.windowName) + "Win"
+
+
+class IsAWindow(Predicate):
+
+ """Predicate subclass that looks for top-level windows"""
+
+ def __init__(self):
+ self.satisfiedByNode = lambda node: node.roleName == 'frame'
+
+ def describeSearchResult(self):
+ return "window"
+
+
+class IsADialogNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level dialog by name"""
+
+ def __init__(self, dialogName):
+ self.dialogName = TranslatableString(dialogName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'dialog' and stringMatches(self.dialogName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s dialog' % self.dialogName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "dialog(%s%s)" % (self.dialogName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.dialogName) + "Dlg"
+
+
+class IsLabelledBy(Predicate):
+
+ """Predicate: is this node labelled by another node"""
+ pass
+
+
+class IsLabelledAs(Predicate):
+
+ """Predicate: is this node labelled with the text string (i.e. by another node with that as a name)"""
+
+ def __init__(self, labelText):
+ self.labelText = TranslatableString(labelText)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # FIXME
+ if node.labeller:
+ return stringMatches(self.labelText, node.labeller.name)
+ else:
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return 'labelled %s' % self.labelText
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(label=%s%s)" % (self.labelText, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.labelText) + "Node"
+
+
+class IsAMenuNamed(Predicate):
+
+ """Predicate subclass that looks for a menu by name"""
+
+ def __init__(self, menuName):
+ self.menuName = TranslatableString(menuName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'menu' and \
+ stringMatches(self.menuName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menu' % (self.menuName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menu(%s%s)" % (self.menuName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuName) + "Menu"
+
+
+class IsAMenuItemNamed(Predicate):
+
+ """Predicate subclass that looks for a menu item by name"""
+
+ def __init__(self, menuItemName):
+ self.menuItemName = TranslatableString(menuItemName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: \
+ node.roleName.endswith('menu item') and \
+ stringMatches(self.menuItemName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menuitem' % (self.menuItemName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menuItem(%s%s)" % (self.menuItemName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuItemName) + "MenuItem"
+
+
+class IsATextEntryNamed(Predicate):
+
+ """Predicate subclass that looks for a text entry by name"""
+
+ def __init__(self, textEntryName):
+ self.textEntryName = TranslatableString(textEntryName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'text' and \
+ stringMatches(self.textEntryName, node.name)
+
+ def describeSearchResult(self):
+ return '%s textentry' % (self.textEntryName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "textentry(%s%s)" % (self.textEntryName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.textEntryName) + "Entry"
+
+
+class IsAButtonNamed(Predicate):
+
+ """Predicate subclass that looks for a button by name"""
+
+ def __init__(self, buttonName):
+ self.buttonName = TranslatableString(buttonName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'push button' \
+ and stringMatches(self.buttonName, node.name)
+
+ def describeSearchResult(self):
+ return '%s button' % (self.buttonName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "button(%s%s)" % (self.buttonName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.buttonName) + "Button"
+
+
+class IsATabNamed(Predicate):
+
+ """Predicate subclass that looks for a tab by name"""
+
+ def __init__(self, tabName):
+ self.tabName = TranslatableString(tabName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'page tab' and \
+ stringMatches(self.tabName, node.name)
+
+ def describeSearchResult(self):
+ return '%s tab' % (self.tabName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "tab(%s%s)" % (self.tabName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.tabName) + "Tab"
diff --git a/build/lib.linux-x86_64-2.7/dogtail/procedural.py b/build/lib.linux-x86_64-2.7/dogtail/procedural.py
new file mode 100644
index 00000000000..c87ae86181c
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/procedural.py
@@ -0,0 +1,455 @@
+"""
+Dogtail's procedural UI
+All the classes here are intended to be single-instance, except for Action.
+"""
+__author__ = 'Zack Cerza <zcerza@redhat.com>'
+#
+#
+# WARNING: Here There Be Dragons (TM) #
+#
+# If you don't understand how to use this API, you almost certainly don't #
+# want to read the code first. We make use of some very non-intuitive #
+# features of Python in order to make the API very simplistic. Therefore, #
+# you should probably only read this code if you're already familiar with #
+# some of Python's advanced features. You have been warned. ;) #
+#
+#
+
+import tree
+import predicate
+from config import config
+from utils import Lock
+import rawinput
+
+#FocusError = "FocusError: %s not found"
+
+
+class FocusError(Exception):
+ pass
+
+import errors
+
+
+def focusFailed(pred):
+ errors.warn('The requested widget could not be focused: %s' %
+ pred.debugName)
+
+ENOARGS = "At least one argument is needed"
+
+
+class FocusBase(object):
+
+ """
+ The base for every class in the module. Does nothing special, really.
+ """
+ node = None
+
+ def __getattr__(self, name):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ try:
+ return getattr(self.node, name)
+ except AttributeError:
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ if name == 'node':
+ setattr(self.__class__, name, value)
+ else:
+ try:
+ setattr(self.node, name, value)
+ except AttributeError:
+ raise AttributeError(name)
+
+
+class FocusApplication (FocusBase):
+
+ """
+ Keeps track of which application is currently focused.
+ """
+ desktop = tree.root
+
+ def __call__(self, name):
+ """
+ Search for an application that matches and refocus on the given name.
+ """
+ try:
+ pred = predicate.IsAnApplicationNamed(name)
+ app = self.desktop.findChild(
+ pred, recursive=False, retry=False)
+ except tree.SearchError:
+ if config.fatalErrors:
+ raise FocusError(name)
+ else:
+ focusFailed(pred)
+ return False
+ if app:
+ FocusApplication.node = app
+ FocusDialog.node = None
+ FocusWindow.node = None
+ FocusWidget.node = None
+ return True
+
+
+class FocusDesktop (FocusBase):
+
+ """
+ This isn't used yet, and may never be used.
+ """
+ pass
+
+
+class FocusWindow (FocusBase):
+
+ """
+ Keeps track of which window is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsAWindowNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWindow.node = result
+ FocusDialog.node = None
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusDialog (FocusBase):
+
+ """
+ Keeps track of which dialog is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsADialogNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusDialog.node = result
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusWidget (FocusBase):
+
+ """
+ Keeps track of which widget is currently focused.
+ """
+
+ def findByPredicate(self, pred):
+ result = None
+ try:
+ result = FocusWidget.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusDialog.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusWindow.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, retry=False)
+ if result:
+ FocusWidget.node = result
+ except AttributeError:
+ if config.fatalErrors:
+ raise FocusError(pred)
+ else:
+ focusFailed(pred)
+ return False
+
+ if result is None:
+ FocusWidget.node = result
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+ def __call__(self, name='', roleName='', description=''):
+ """
+ If name, roleName or description are specified, search for a widget that matches and refocus on it.
+ """
+ if not name and not roleName and not description:
+ raise TypeError(ENOARGS)
+
+ # search for a widget.
+ pred = predicate.GenericPredicate(name=name,
+ roleName=roleName, description=description)
+ return self.findByPredicate(pred)
+
+
+class Focus (FocusBase):
+
+ """
+ The container class for the focused application, dialog and widget.
+ """
+
+ def __getattr__(self, name):
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ if name in ('application', 'dialog', 'widget', 'window'):
+ self.__dict__[name] = value
+ else:
+ raise AttributeError(name)
+
+ desktop = tree.root
+ application = FocusApplication()
+ app = application # shortcut :)
+ dialog = FocusDialog()
+ window = FocusWindow()
+ frame = window
+ widget = FocusWidget()
+
+ def button(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+
+ def icon(self, name):
+ """
+ A shortcut to self.widget(name, roleName = 'icon')
+ """
+ return self.widget(name=name, roleName='icon')
+
+ def menu(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+
+ def menuItem(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+
+ def table(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table')
+ """
+ return self.widget(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table cell')
+ """
+ return self.widget(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self.widget.findByPredicate(IsATextEntryNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsATextEntryNamed(name))
+
+
+class Action (FocusWidget):
+
+ """
+ Aids in executing AT-SPI actions, refocusing the widget if necessary.
+ """
+
+ def __init__(self, action):
+ """
+ action is a string with the same name as the AT-SPI action you wish to execute using this class.
+ """
+ self.action = action
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ self.node.doActionNamed(self.action)
+
+ def __getattr__(self, attr):
+ return getattr(FocusWidget.node, attr)
+
+ def __setattr__(self, attr, value):
+ if attr == 'action':
+ self.__dict__[attr] = value
+ else:
+ setattr(FocusWidget, attr, value)
+
+ def button(self, name):
+ """
+ A shortcut to self(name, roleName = 'push button')
+ """
+ self.__call__(name=name, roleName='push button')
+
+ def menu(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu')
+ """
+ self.__call__(name=name, roleName='menu')
+
+ def menuItem(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu item')
+ """
+ self.__call__(name=name, roleName='menu item')
+
+ def table(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table')
+ """
+ self.__call__(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table cell')
+ """
+ self.__call__(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self(name, roleName = 'text')
+ """
+ self.__call__(name=name, roleName='text')
+
+
+class Click (Action):
+
+ """
+ A special case of Action, Click will eventually handle raw mouse events.
+ """
+ primary = 1
+ middle = 2
+ secondary = 3
+
+ def __init__(self):
+ Action.__init__(self, 'click')
+
+ def __call__(self, name='', roleName='', description='', raw=True, button=primary, delay=config.actionDelay):
+ """
+ By default, execute a raw mouse event.
+ If raw is False or if button evaluates to False, just pass the rest of
+ the arguments to Action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ if raw and button:
+ # We're doing a raw mouse click
+ Click.node.click(button)
+ else:
+ Action.__call__(
+ self, name=name, roleName=roleName, description=description, delay=delay)
+
+
+class Select (Action):
+
+ """
+ Aids in selecting and deselecting widgets, i.e. page tabs
+ """
+ select = 'select'
+ deselect = 'deselect'
+
+ def __init__(self, action):
+ """
+ action must be 'select' or 'deselect'.
+ """
+ if action not in (self.select, self.deselect):
+ raise ValueError(action)
+ Action.__init__(self, action)
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ func = getattr(self.node, self.action)
+ func()
+
+
+def type(text):
+ if focus.widget.node:
+ focus.widget.node.typeText(text)
+ else:
+ rawinput.typeText(text)
+
+
+def keyCombo(combo):
+ if focus.widget.node:
+ focus.widget.node.keyCombo(combo)
+ else:
+ rawinput.keyCombo(combo)
+
+
+def run(application, arguments='', appName=''):
+ from utils import run as utilsRun
+ pid = utilsRun(application + ' ' + arguments, appName=appName)
+ focus.application(application)
+ return pid
+
+import os
+# tell sniff not to use auto-refresh while script using this module is running
+# may have already been locked by dogtail.tree
+if not os.path.exists('/tmp/sniff_refresh.lock'): # pragma: no cover
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError:
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+focus = Focus()
+click = Click()
+activate = Action('activate')
+openItem = Action('open')
+menu = Action('menu')
+select = Select(Select.select)
+deselect = Select(Select.deselect)
diff --git a/build/lib.linux-x86_64-2.7/dogtail/rawinput.py b/build/lib.linux-x86_64-2.7/dogtail/rawinput.py
new file mode 100644
index 00000000000..7100b29bfae
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/rawinput.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+"""
+Handles raw input using AT-SPI event generation.
+
+Note: Think of keyvals as keysyms, and keynames as keystrings.
+
+Authors: David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>
+"""
+
+__author__ = """
+David Malcolm <dmalcolm@redhat.com>,
+Zack Cerza <zcerza@redhat.com>
+"""
+import gi
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gdk
+from config import config
+from utils import doDelay
+from logging import debugLogger as logger
+from pyatspi import Registry as registry
+from pyatspi import (KEY_SYM, KEY_PRESS, KEY_PRESSRELEASE, KEY_RELEASE)
+from exceptions import ValueError
+from __builtin__ import unicode, unichr
+
+
+def doTypingDelay():
+ doDelay(config.typingDelay)
+
+
+def checkCoordinates(x, y):
+ if x < 0 or y < 0:
+ raise ValueError(
+ "Attempting to generate a mouse event at negative coordinates: (%s,%s)" % (x, y))
+
+
+def click(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s click at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sc' % button)
+ doDelay(config.actionDelay)
+
+
+def doubleClick(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button double-click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s doubleclick at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sd' % button)
+ doDelay()
+
+
+def press(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button press at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s press at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sp' % button)
+ doDelay()
+
+
+def release(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button release at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s release at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sr' % button)
+ doDelay()
+
+
+def absoluteMotion(x, y, mouseDelay=None, check=True):
+ """
+ Synthesize mouse absolute motion to (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse absolute motion to (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def relativeMotion(x, y, mouseDelay=None):
+ logger.log("Mouse relative motion of (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'rel')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def drag(fromXY, toXY, button=1, check=True):
+ """
+ Synthesize a mouse press, drag, and release on the screen.
+ """
+ logger.log("Mouse button %s drag from %s to %s" % (button, fromXY, toXY))
+
+ (x, y) = fromXY
+ press(x, y, button, check)
+ # doDelay()
+
+ (x, y) = toXY
+ absoluteMotion(x, y, check=check)
+ doDelay()
+
+ release(x, y, button, check)
+ doDelay()
+
+
+def typeText(string):
+ """
+ Types the specified string, one character at a time.
+ Please note, you may have to set a higher typing delay,
+ if your machine misses/switches the characters typed.
+ Needed sometimes on slow setups/VMs typing non-ASCII utf8 chars.
+ """
+ if not isinstance(string, unicode):
+ string = string.decode('utf-8')
+ for char in string:
+ pressKey(char)
+
+keyNameAliases = {
+ 'enter': 'Return',
+ 'esc': 'Escape',
+ 'alt': 'Alt_L',
+ 'control': 'Control_L',
+ 'ctrl': 'Control_L',
+ 'shift': 'Shift_L',
+ 'del': 'Delete',
+ 'ins': 'Insert',
+ 'pageup': 'Page_Up',
+ 'pagedown': 'Page_Down',
+ ' ': 'space',
+ '\t': 'Tab',
+ '\n': 'Return'
+}
+
+
+# TODO: Dead code
+def keySymToUniChar(keySym): # pragma: no cover
+ i = Gdk.keyval_to_unicode(keySym)
+ if i:
+ UniChar = unichr(i)
+ else:
+ UniChar = ''
+ return UniChar
+
+
+def uniCharToKeySym(uniChar):
+ # OK, if it's not actually unicode we can fix that, right?
+ if not isinstance(uniChar, unicode):
+ uniChar = unicode(uniChar, 'utf-8')
+ i = ord(uniChar)
+ keySym = Gdk.unicode_to_keyval(i)
+ return keySym
+
+
+# dead code
+def keySymToKeyName(keySym): # pragma: no cover
+ return Gdk.keyval_name(keySym)
+
+
+def keyNameToKeySym(keyName):
+ keyName = keyNameAliases.get(keyName.lower(), keyName)
+ keySym = Gdk.keyval_from_name(keyName)
+ # various error 'codes' returned for non-recognized chars in versions of GTK3.X
+ if keySym == 0xffffff or keySym == 0x0 or keySym is None:
+ try:
+ keySym = uniCharToKeySym(keyName)
+ except: # not even valid utf-8 char
+ try: # Last attempt run at a keyName ('Meta_L', 'Dash' ...)
+ keySym = getattr(Gdk, 'KEY_' + keyName)
+ except AttributeError:
+ raise KeyError(keyName)
+ return keySym
+
+
+def keyNameToKeyCode(keyName):
+ """
+ Use GDK to get the keycode for a given keystring.
+
+ Note that the keycode returned by this function is often incorrect when
+ the requested keystring is obtained by holding down the Shift key.
+
+ Generally you should use uniCharToKeySym() and should only need this
+ function for nonprintable keys anyway.
+ """
+ keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default())
+ entries = keymap.get_entries_for_keyval(
+ Gdk.keyval_from_name(keyName))
+ try:
+ return entries[1][0].keycode
+ except TypeError:
+ pass
+
+
+def pressKey(keyName):
+ """
+ Presses (and releases) the key specified by keyName.
+ keyName is the English name of the key as seen on the keyboard. Ex: 'enter'
+ Names are looked up in Gdk.KEY_ If they are not found there, they are
+ looked up by uniCharToKeySym().
+ """
+ keySym = keyNameToKeySym(keyName)
+ registry.generateKeyboardEvent(keySym, None, KEY_SYM)
+ doTypingDelay()
+
+
+def keyCombo(comboString):
+ """
+ Generates the appropriate keyboard events to simulate a user pressing the
+ specified key combination.
+
+ comboString is the representation of the key combo to be generated.
+ e.g. '<Control><Alt>p' or '<Control><Shift>PageUp' or '<Control>q'
+ """
+ strings = []
+ for s in comboString.split('<'):
+ if s:
+ for S in s.split('>'):
+ if S:
+ S = keyNameAliases.get(S.lower(), S)
+ strings.append(S)
+ for s in strings:
+ if not hasattr(Gdk, s):
+ if not hasattr(Gdk, 'KEY_' + s):
+ raise ValueError("Cannot find key %s" % s)
+ modifiers = strings[:-1]
+ finalKey = strings[-1]
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_PRESS)
+ code = keyNameToKeyCode(finalKey)
+ registry.generateKeyboardEvent(code, None, KEY_PRESSRELEASE)
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_RELEASE)
+ doDelay()
diff --git a/build/lib.linux-x86_64-2.7/dogtail/sessions.py b/build/lib.linux-x86_64-2.7/dogtail/sessions.py
new file mode 100644
index 00000000000..8d4bcce6a37
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/sessions.py
@@ -0,0 +1,231 @@
+import time
+import os
+import pwd
+import errno
+import re
+import subprocess
+import signal
+import tempfile
+import random
+import glob
+from dogtail.config import config
+
+
+def scratchFile(label): # pragma: no cover
+ """Uses tempfile.NamedTemporaryFile() to create a unique tempfile in
+ config.scratchDir, with a filename like:
+ dogtail-headless-<label>.<random junk>"""
+ prefix = "dogtail-headless-"
+ return tempfile.NamedTemporaryFile(prefix="%s%s." % (prefix, label),
+ dir=config.scratchDir)
+
+
+def testBinary(path): # pragma: no cover
+ if (path.startswith(os.path.sep) or
+ path.startswith(os.path.join('.', '')) or
+ path.startswith(os.path.join('..', ''))):
+ if not os.path.exists(path):
+ raise IOError(errno.ENOENT, "No such file", path)
+ if not os.access(path, os.X_OK):
+ raise IOError(errno.ENOEXEC, "Permission denied", path)
+ return True
+
+
+def get_username(): # pragma: no cover
+ return pwd.getpwuid(os.getuid())[0]
+
+
+class Subprocess(object): # pragma: no cover
+
+ def __init__(self, cmdList, environ=None):
+ testBinary(cmdList[0])
+ self.cmdList = cmdList
+ self.environ = environ
+ self._exitCode = None
+
+ def start(self):
+ if self.environ is None:
+ self.environ = os.environ
+ self.popen = subprocess.Popen(
+ self.cmdList, env=self.environ) # , stdout = subprocess.PIPE,
+ # stderr = subprocess.STDOUT, close_fds = True)
+ return self.popen.pid
+
+ def wait(self):
+ return self.popen.wait()
+
+ def stop(self):
+ # The following doesn't exist in python < 2.6, if you can believe it.
+ # self.popen.terminate()
+ os.kill(self.popen.pid, signal.SIGTERM)
+
+ @property
+ def exitCode(self):
+ if self._exitCode is None:
+ self._exitCode = self.wait()
+ return self._exitCode
+
+
+class XServer(Subprocess): # pragma: no cover
+
+ def __init__(self, server="/usr/bin/Xorg",
+ xinitrc="/etc/X11/xinit/Xclients",
+ resolution="1024x768x16"):
+ """resolution is only used with Xvfb."""
+ testBinary(server)
+ self.server = server
+ self._exitCode = None
+ self.xinit = "/usr/bin/xinit"
+ self.display = None
+ self.xinitrc = xinitrc
+ self.resolution = resolution
+
+ @staticmethod
+ def findFreeDisplay():
+ tmp = os.listdir('/tmp')
+ pattern = re.compile('\.X([0-9]+)-lock')
+ usedDisplays = []
+ for file in tmp:
+ match = re.match(pattern, file)
+ if match:
+ usedDisplays.append(int(match.groups()[0]))
+ if not usedDisplays:
+ return ':0'
+ usedDisplays.sort()
+ return ':' + str(usedDisplays[-1] + 1)
+
+ @property
+ def cmdList(self):
+ self.display = self.findFreeDisplay()
+ cmd = []
+ if self.xinit:
+ cmd.append(self.xinit)
+ if self.xinitrc:
+ cmd.append(self.xinitrc)
+ cmd.append('--')
+ cmd.append(self.server)
+ cmd.append(self.display)
+ cmd.extend(['-ac', '-noreset'])
+ if self.server.endswith('Xvfb'):
+ cmd.extend(['-screen', '0', self.resolution])
+ cmd.append('-shmem')
+ return cmd
+
+ def start(self):
+ print(' '.join(self.cmdList))
+ self.popen = subprocess.Popen(self.cmdList)
+ return self.popen.pid
+
+
+class Script(Subprocess): # pragma: no cover
+ pass
+
+
+class Session(object): # pragma: no cover
+
+ cookieName = "DOGTAIL_SESSION_COOKIE"
+
+ def __init__(self, sessionBinary, scriptCmdList=[], scriptDelay=20, logout=True):
+ testBinary(sessionBinary)
+ self.sessionBinary = sessionBinary
+ self.script = Script(scriptCmdList)
+ self.scriptDelay = scriptDelay
+ self.logout = logout
+ self.xserver = XServer()
+ self._cookie = None
+ self._environment = None
+
+ def start(self):
+ self.xinitrcFileObj = scratchFile('xinitrc')
+ self.xserver.xinitrc = self.xinitrcFileObj.name
+ self._buildXInitRC(self.xinitrcFileObj)
+ xServerPid = self.xserver.start()
+ time.sleep(self.scriptDelay)
+ self.script.environ = self.environment
+ scriptPid = self.script.start()
+ return (xServerPid, scriptPid)
+
+ @property
+ def environment(self):
+ def isSessionProcess(fileName):
+ try:
+ if os.path.realpath(path + 'exe') != ('/usr/bin/plasma-desktop'
+ if self.sessionBinary.split('/')[-1] == 'startkde'
+ else self.sessionBinary):
+ return False
+ except OSError:
+ return False
+ pid = fileName.split('/')[2]
+ if pid == 'self' or pid == str(os.getpid()):
+ return False
+ return True
+
+ def getEnvDict(fileName):
+ try:
+ envString = open(fileName, 'r').read()
+ except IOError:
+ return {}
+ envItems = envString.split('\x00')
+ envDict = {}
+ for item in envItems:
+ if not '=' in item:
+ continue
+ k, v = item.split('=', 1)
+ envDict[k] = v
+ return envDict
+
+ def isSessionEnv(envDict):
+ if not envDict:
+ return False
+ if envDict.get(self.cookieName, 'notacookie') == self.cookie:
+ return True
+ return False
+
+ for path in glob.glob('/proc/*/'):
+ if not isSessionProcess(path):
+ continue
+ envFile = path + 'environ'
+ envDict = getEnvDict(envFile)
+ if isSessionEnv(envDict):
+ # print path
+ # print envDict
+ self._environment = envDict
+ if not self._environment:
+ raise RuntimeError("Can't find our environment!")
+ return self._environment
+
+ def wait(self):
+ self.script.wait()
+ return self.xserver.wait()
+
+ def stop(self):
+ try:
+ self.script.stop()
+ except OSError:
+ pass
+ self.xserver.stop()
+
+ def attemptLogout(self):
+ logoutScript = Script('dogtail-logout',
+ environ=self.environment)
+ logoutScript.start()
+ logoutScript.wait()
+
+ @property
+ def cookie(self):
+ if not self._cookie:
+ self._cookie = "%X" % random.getrandbits(16)
+ return self._cookie
+
+ def _buildXInitRC(self, fileObj):
+ lines = [
+ "export %s=%s" % (self.cookieName, self.cookie),
+ "gsettings set org.gnome.desktop.interface toolkit-accessibility true",
+ ". /etc/X11/xinit/xinitrc-common",
+ "export %s" % self.cookieName,
+ "exec -l $SHELL -c \"$CK_XINIT_SESSION $SSH_AGENT %s\"" %
+ (self.sessionBinary),
+ ""]
+
+ fileObj.write('\n'.join(lines).strip())
+ fileObj.flush()
diff --git a/build/lib.linux-x86_64-2.7/dogtail/tc.py b/build/lib.linux-x86_64-2.7/dogtail/tc.py
new file mode 100644
index 00000000000..c7ef9f5bc23
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/tc.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+"""Test Case magic
+
+Author: Ed Rousseau <rousseau@redhat.com>"""
+__author__ = "Ed Rousseau <rousseau@redhat.com>"
+
+import os
+import os.path
+from config import config
+from logging import ResultsLogger, TimeStamp
+from PIL import Image, ImageChops, ImageStat
+from __builtin__ import unicode, long
+
+
+class TC(object): # pragma: no cover
+
+ """
+ The Test Case Superclass
+ """
+ logger = ResultsLogger()
+
+ def __init__(self):
+ self.encoding = config.encoding
+ # ascii + unicode. 8 bit extended char has been ripped out
+ self.supportedtypes = (
+ "ascii", "utf-8", "utf-16", "utf-16-be", "utf-16-le", "unicode-escape", "raw-unicode-escape",
+ "big5", "gb18030", "eucJP", "eucKR", "shiftJIS")
+
+ # String comparison function
+ def compare(self, label, baseline, undertest, encoding=config.encoding):
+ """
+ Compares 2 strings to see if they are the same. The user may specify
+ the encoding to which the two strings are to be normalized for the
+ comparison. Default encoding is the default system encoding.
+ Normalization to extended 8 bit charactersets is not supported.
+
+ When the origin of either baseline or undertest is a text file whose
+ encoding is something other than ASCII, it is necessary to use
+ codecs.open() instead of open(), so the file's encoding may be
+ specified.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ for string in [self.baseline, self.undertest]:
+ try:
+ string = unicode(string, 'utf-8')
+ except TypeError:
+ pass
+ self.encoding = encoding
+
+ # Normalize the encoding type for the comparaison based on
+ # self.encoding
+ if self.encoding in self.supportedtypes:
+ self.baseline = (self.baseline).encode(self.encoding)
+ self.undertest = (self.undertest).encode(self.encoding)
+ # Compare the strings
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed"}
+ else:
+ self.result = {self.label: "Failed - " + self.encoding +
+ " strings do not match. " + self.baseline + " expected: Got " + self.undertest}
+ # Pass the test result to the ResultsLogger for writing
+ TC.logger.log(self.result)
+ return self.result
+
+ else:
+ # We should probably raise an exception here
+ self.result = {
+ self.label: "ERROR - " + self.encoding + " is not a supported encoding type"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+# String Test Case subclass
+class TCString(TC): # pragma: no cover
+
+ """
+ String Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+
+# Image test case subclass
+
+
+class TCImage(TC): # pragma: no cover
+
+ """
+ Image Test Case Class.
+ """
+
+ def compare(self, label, baseline, undertest):
+ for _file in (baseline, undertest):
+ if type(_file) is not unicode and type(_file) is not str:
+ raise TypeError("Need filenames!")
+ self.label = label.strip()
+ self.baseline = baseline.strip()
+ self.undertest = undertest.strip()
+ diffName = TimeStamp().fileStamp("diff") + ".png"
+ self.diff = os.path.normpath(
+ os.path.sep.join((config.scratchDir, diffName)))
+
+ self.baseImage = Image.open(self.baseline)
+ self.testImage = Image.open(self.undertest)
+ try:
+ if self.baseImage.size != self.testImage.size:
+ self.result = {
+ self.label: "Failed - images are different sizes"}
+ raise StopIteration
+
+ self.diffImage = ImageChops.difference(self.baseImage,
+ self.testImage)
+ self.diffImage.save(self.diff)
+ result = False
+ for stat in ('stddev', 'mean', 'sum2'):
+ for item in getattr(ImageStat.Stat(self.diffImage), stat):
+ if item:
+ self.result = {self.label: "Failed - see %s" %
+ self.diff}
+ raise StopIteration
+ else:
+ result = True
+ except StopIteration:
+ result = False
+
+ if result:
+ self.result = {self.label: "Passed"}
+
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCNumber(TC): # pragma: no cover
+
+ """
+ Number Comparaison Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+ self.supportedtypes = ("int", "long", "float", "complex", "oct", "hex")
+
+ # Compare 2 numbers by the type provided in the type arg
+ def compare(self, label, baseline, undertest, type):
+ """
+ Compares 2 numbers to see if they are the same. The user may specify
+ how to normalize mixed type comparisons via the type argument.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ self.type = type.strip()
+
+ # If we get a valid type, convert to that type and compare
+ if self.type in self.supportedtypes:
+ # Normalize for comparison
+ if self.type == "int":
+ self.baseline = int(self.baseline)
+ self.undertest = int(self.undertest)
+ elif self.type == "long":
+ self.baseline = long(self.baseline)
+ self.undertest = long(self.undertest)
+ elif self.type == "float":
+ self.baseline = float(self.baseline)
+ self.undertest = float(self.undertest)
+ else:
+ self.baseline = complex(self.baseline)
+ self.undertest = complex(self.undertest)
+
+ # compare
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed - numbers are the same"}
+ else:
+ self.result = {self.label: "Failed - " + str(
+ self.baseline) + " expected: Got " + str(self.undertest)}
+ TC.logger.log(self.result)
+ return self.result
+ else:
+ self.result = {
+ self.label: "Failed - " + self.type + " is not in list of supported types"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCBool(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, _bool):
+ """
+ If _bool is True, pass.
+ If _bool is False, fail.
+ """
+ if type(_bool) is not bool:
+ raise TypeError
+ if _bool:
+ result = {label: "Passed"}
+ else:
+ result = {label: "Failed"}
+ TC.logger.log(result)
+
+from tree import Node
+
+
+class TCNode(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, baseline, undertest):
+ """
+ If baseline is None, simply check that undertest is a Node.
+ If baseline is a Node, check that it is equal to undertest.
+ """
+ if baseline is not None and not isinstance(baseline, Node):
+ raise TypeError
+
+ if not isinstance(undertest, Node):
+ result = {label: "Failed - %s is not a Node" % undertest}
+ elif baseline is None:
+ result = {label: "Passed - %s is a Node" % undertest}
+ elif isinstance(baseline, Node):
+ if baseline == undertest:
+ result = {label: "Passed - %s == %s" % (baseline, undertest)}
+ else:
+ result = {label: "Failed - %s != %s" % (baseline, undertest)}
+ TC.logger.log(result)
diff --git a/build/lib.linux-x86_64-2.7/dogtail/tree.py b/build/lib.linux-x86_64-2.7/dogtail/tree.py
new file mode 100644
index 00000000000..e6050bbb98b
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/tree.py
@@ -0,0 +1,1318 @@
+"""Makes some sense of the AT-SPI API
+
+The tree API handles various things for you:
+ - fixes most timing issues
+ - can automatically generate (hopefully) highly-readable logs of what the
+script is doing
+ - traps various UI malfunctions, raising exceptions for them (again,
+hopefully improving the logs)
+
+The most important class is Node. Each Node is an element of the desktop UI.
+There is a tree of nodes, starting at 'root', with applications as its
+children, with the top-level windows and dialogs as their children. The various
+widgets that make up the UI appear as descendents in this tree. All of these
+elements (root, the applications, the windows, and the widgets) are represented
+as instances of Node in a tree (provided that the program of interest is
+correctly exporting its user-interface to the accessibility system). The Node
+class is a mixin for Accessible and the various Accessible interfaces.
+
+The Action class represents an action that the accessibility layer exports as
+performable on a specific node, such as clicking on it. It's a wrapper around
+Accessibility.Action.
+
+We often want to look for a node, based on some criteria, and this is provided
+by the Predicate class.
+
+Dogtail implements a high-level searching system, for finding a node (or
+nodes) satisfying whatever criteria you are interested in. It does this with
+a 'backoff and retry' algorithm. This fixes most timing problems e.g. when a
+dialog is in the process of opening but hasn't yet done so.
+
+If a search fails, it waits 'config.searchBackoffDuration' seconds, and then
+tries again, repeatedly. After several failed attempts (determined by
+config.searchWarningThreshold) it will start sending warnings about the search
+to the debug log. If it still can't succeed after 'config.searchCutoffCount'
+attempts, it raises an exception containing details of the search. You can see
+all of this process in the debug log by setting 'config.debugSearching' to True
+
+We also automatically add a short delay after each action
+('config.defaultDelay' gives the time in seconds). We'd hoped that the search
+backoff and retry code would eliminate the need for this, but unfortunately we
+still run into timing issues. For example, Evolution (and probably most
+other apps) set things up on new dialogs and wizard pages as they appear, and
+we can run into 'setting wars' where the app resets the widgetry to defaults
+after our script has already filled out the desired values, and so we lose our
+values. So we give the app time to set the widgetry up before the rest of the
+script runs.
+
+The classes trap various UI malfunctions and raise exceptions that better
+describe what went wrong. For example, they detects attempts to click on an
+insensitive UI element and raise a specific exception for this.
+
+Unfortunately, some applications do not set up the 'sensitive' state
+correctly on their buttons (e.g. Epiphany on form buttons in a web page). The
+current workaround for this is to set config.ensureSensitivity=False, which
+disables the sensitivity testing.
+
+Authors: Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+from config import config
+if config.checkForA11y:
+ from utils import checkForA11y
+ checkForA11y()
+
+import predicate
+from time import sleep
+from utils import doDelay
+from utils import Blinker
+from utils import Lock
+import rawinput
+import path
+from __builtin__ import xrange
+
+from logging import debugLogger as logger
+
+try:
+ import pyatspi
+ import Accessibility
+except ImportError: # pragma: no cover
+ raise ImportError("Error importing the AT-SPI bindings")
+
+# We optionally import the bindings for libWnck.
+try:
+ from gi.repository import Wnck
+ gotWnck = True # pragma: no cover
+except ImportError:
+ # Skip this warning, since the functionality is almost entirely nonworking anyway.
+ # print "Warning: Dogtail could not import the Python bindings for
+ # libwnck. Window-manager manipulation will not be available."
+ gotWnck = False
+
+from gi.repository import GLib
+
+haveWarnedAboutChildrenLimit = False
+
+
+class SearchError(Exception):
+ pass
+
+
+class NotSensitiveError(Exception):
+
+ """
+ The widget is not sensitive.
+ """
+ message = "Cannot %s %s. It is not sensitive."
+
+ def __init__(self, action):
+ self.action = action
+
+ def __str__(self):
+ return self.message % (self.action.name, self.action.node.getLogString())
+
+
+class ActionNotSupported(Exception):
+
+ """
+ The widget does not support the requested action.
+ """
+ message = "Cannot do '%s' action on %s"
+
+ def __init__(self, actionName, node):
+ self.actionName = actionName
+ self.node = node
+
+ def __str__(self):
+ return self.message % (self.actionName, self.node.getLogString())
+
+
+class Action(object):
+
+ """
+ Class representing an action that can be performed on a specific node
+ """
+ # Valid types of actions we know about. Feel free to add any you see.
+ types = ('click',
+ 'press',
+ 'release',
+ 'activate',
+ 'jump',
+ 'check',
+ 'dock',
+ 'undock',
+ 'open',
+ 'menu')
+
+ def __init__(self, node, action, index):
+ self.node = node
+ self.__action = action
+ self.__index = index
+
+ @property
+ def name(self):
+ return self.__action.getName(self.__index)
+
+ @property
+ def description(self):
+ return self.__action.getDescription(self.__index)
+
+ @property
+ def keyBinding(self):
+ return self.__action.getKeyBinding(self.__index)
+
+ def __str__(self):
+ return "[action | %s | %s ]" % \
+ (self.name, self.keyBinding)
+
+ def do(self):
+ """
+ Performs the given tree.Action, with appropriate delays and logging.
+ """
+ logger.log("%s on %s" % (self.name, self.node.getLogString()))
+ if not self.node.sensitive:
+ if config.ensureSensitivity:
+ raise NotSensitiveError(self)
+ else:
+ nSE = NotSensitiveError(self)
+ logger.log("Warning: " + str(nSE))
+ if config.blinkOnActions:
+ self.node.blink()
+ result = self.__action.doAction(self.__index)
+ doDelay(config.actionDelay)
+ return result
+
+
+class Node(object):
+
+ """
+ A node in the tree of UI elements. This class is mixed in with
+ Accessibility.Accessible to both make it easier to use and to add
+ additional functionality. It also has a debugName which is set up
+ automatically when doing searches.
+ """
+
+ def __setupUserData(self):
+ try:
+ len(self.user_data)
+ except (AttributeError, TypeError):
+ self.user_data = {}
+
+ def debugName():
+ doc = "debug name assigned during search operations"
+
+ def fget(self):
+ self.__setupUserData()
+ return self.user_data.get('debugName', None)
+
+ def fset(self, debugName):
+ self.__setupUserData()
+ self.user_data['debugName'] = debugName
+
+ return property(**locals())
+ debugName = debugName()
+ #
+ # Accessible
+ #
+
+ @property
+ def dead(self):
+ """Is the node dead (defunct) ?"""
+ try:
+ if self.roleName == 'invalid':
+ return True
+ self.role
+ self.name
+ if len(self) > 0:
+ self[0]
+ except:
+ return True
+ return False
+
+ @property
+ def children(self):
+ """a list of this Accessible's children"""
+ if self.parent and self.parent.roleName == 'hyper link':
+ print(self.parent.role)
+ return []
+ children = []
+ childCount = self.childCount
+ if childCount > config.childrenLimit:
+ global haveWarnedAboutChildrenLimit
+ if not haveWarnedAboutChildrenLimit:
+ logger.log("Only returning %s children. You may change "
+ "config.childrenLimit if you wish. This message will only"
+ " be printed once." % str(config.childrenLimit))
+ haveWarnedAboutChildrenLimit = True
+ childCount = config.childrenLimit
+ for i in range(childCount):
+ # Workaround for GNOME bug #465103
+ # also solution for GNOME bug #321273
+ try:
+ child = self[i]
+ except LookupError:
+ child = None
+ if child:
+ children.append(child)
+
+ invalidChildren = childCount - len(children)
+ if invalidChildren and config.debugSearching:
+ logger.log("Skipped %s invalid children of %s" %
+ (invalidChildren, str(self)))
+ try:
+ ht = self.queryHypertext()
+ for li in range(ht.getNLinks()):
+ link = ht.getLink(li)
+ for ai in range(link.nAnchors):
+ child = link.getObject(ai)
+ child.__setupUserData()
+ child.user_data['linkAnchor'] = \
+ LinkAnchor(node=child,
+ hypertext=ht,
+ linkIndex=li,
+ anchorIndex=ai)
+ children.append(child)
+ except (NotImplementedError, AttributeError):
+ pass
+
+ return children
+
+ roleName = property(Accessibility.Accessible.getRoleName)
+
+ role = property(Accessibility.Accessible.getRole)
+
+ indexInParent = property(Accessibility.Accessible.getIndexInParent)
+
+ #
+ # Action
+ #
+
+ # Needed to be renamed from doAction due to conflicts
+ # with 'Accessibility.Accessible.doAction' in gtk3 branch
+ def doActionNamed(self, name):
+ """
+ Perform the action with the specified name. For a list of actions
+ supported by this instance, check the 'actions' property.
+ """
+ actions = self.actions
+ if name in actions:
+ return actions[name].do()
+ raise ActionNotSupported(name, self)
+
+ @property
+ def actions(self):
+ """
+ A dictionary of supported action names as keys, with Action objects as
+ values. Common action names include:
+
+ 'click' 'press' 'release' 'activate' 'jump' 'check' 'dock' 'undock'
+ 'open' 'menu'
+ """
+ actions = {}
+ try:
+ action = self.queryAction()
+ for i in range(action.nActions):
+ a = Action(self, action, i)
+ actions[action.getName(i)] = a
+ finally:
+ return actions
+
+ def combovalue():
+ doc = "The value (as a string) currently selected in the combo box."
+
+ def fget(self):
+ return self.name
+
+ def fset(self, value):
+ logger.log("Setting combobox %s to '%s'" % (self.getLogString(),
+ value))
+ self.childNamed(childName=value).doActionNamed('click')
+ doDelay()
+
+ return property(**locals())
+ combovalue = combovalue()
+ #
+ # Hypertext and Hyperlink
+ #
+
+ @property
+ def URI(self):
+ try:
+ return self.user_data['linkAnchor'].URI
+ except (KeyError, AttributeError):
+ raise NotImplementedError
+
+ #
+ # Text and EditableText
+ #
+ def text():
+ doc = """For instances with an AccessibleText interface, the text as a
+ string. This is read-only, unless the instance also has an
+ AccessibleEditableText interface. In this case, you can write values
+ to the attribute. This will get logged in the debug log, and a delay
+ will be added.
+
+ If this instance corresponds to a password entry, use the passwordText
+ property instead."""
+
+ def fget(self):
+ try:
+ return self.queryText().getText(0, -1)
+ except NotImplementedError:
+ return None
+
+ def fset(self, text):
+ try:
+ if config.debugSearching:
+ msg = "Setting text of %s to %s"
+ # Let's not get too crazy if 'text' is really large...
+ # FIXME: Sometimes the next line screws up Unicode strings.
+ if len(text) > 140:
+ txt = text[:134] + " [...]"
+ else:
+ txt = text
+ logger.log(msg % (self.getLogString(), "'%s'" % txt))
+ self.queryEditableText().setTextContents(text)
+ except NotImplementedError:
+ raise AttributeError("can't set attribute")
+
+ return property(**locals())
+ text = text()
+
+ def caretOffset():
+
+ def fget(self):
+ """For instances with an AccessibleText interface, the caret
+ offset as an integer."""
+ return self.queryText().caretOffset
+
+ def fset(self, offset):
+ return self.queryText().setCaretOffset(offset)
+
+ return property(**locals())
+ caretOffset = caretOffset()
+
+ #
+ # Component
+ #
+
+ @property
+ def position(self):
+ """A tuple containing the position of the Accessible: (x, y)"""
+ return self.queryComponent().getPosition(pyatspi.DESKTOP_COORDS)
+
+ @property
+ def size(self):
+ """A tuple containing the size of the Accessible: (w, h)"""
+ return self.queryComponent().getSize()
+
+ @property
+ def extents(self):
+ """A tuple containing the location and size of the Accessible:
+ (x, y, w, h)"""
+ try:
+ ex = self.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ return (ex.x, ex.y, ex.width, ex.height)
+ except NotImplementedError:
+ return None
+
+ def contains(self, x, y):
+ try:
+ return self.queryComponent().contains(x, y, pyatspi.DESKTOP_COORDS)
+ except NotImplementedError:
+ return False
+
+ def getChildAtPoint(self, x, y):
+ node = self
+ while True:
+ try:
+ child = node.queryComponent().getAccessibleAtPoint(x, y,
+ pyatspi.DESKTOP_COORDS)
+ if child and child.contains(x, y):
+ node = child
+ else:
+ break
+ except NotImplementedError:
+ break
+ if node and node.contains(x, y):
+ return node
+ else:
+ return None
+
+ def grabFocus(self):
+ "Attempts to set the keyboard focus to this Accessible."
+ return self.queryComponent().grabFocus()
+
+ # def blink(self, count=2):
+ #"""
+ # Blink, baby!
+ #"""
+ # if not self.extents: return False
+ # else:
+ #(x, y, w, h) = self.extents
+ #from utils import Blinker
+ #blinkData = Blinker(x, y, w, h, count)
+ # return True
+
+ def click(self, button=1):
+ """
+ Generates a raw mouse click event, using the specified button.
+ - 1 is left,
+ - 2 is middle,
+ - 3 is right.
+ """
+ logger.log("Clicking on %s" % self.getLogString())
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.click(clickX, clickY, button)
+
+ def doubleClick(self, button=1):
+ """
+ Generates a raw mouse double-click event, using the specified button.
+ """
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.doubleClick(clickX, clickY, button)
+
+ def point(self, mouseDelay=None):
+ """
+ Move mouse cursor to the center of the widget.
+ """
+ pointX = self.position[0] + self.size[0] / 2
+ pointY = self.position[1] + self.size[1] / 2
+ logger.log("Pointing on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(pointX), str(pointY)))
+ rawinput.registry.generateMouseEvent(pointX, pointY, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+ #
+ # RelationSet
+ #
+ @property
+ def labeler(self):
+ """'labeller' (read-only list of Node instances):
+ The node(s) that is/are a label for this node. Generated from
+ 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABELLED_BY:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labeller = labeler
+
+ @property
+ def labelee(self):
+ """'labellee' (read-only list of Node instances):
+ The node(s) that this node is a label for. Generated from 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labellee = labelee
+
+ #
+ # StateSet
+ #
+ @property
+ def sensitive(self):
+ """Is the Accessible sensitive (i.e. not greyed out)?"""
+ return self.getState().contains(pyatspi.STATE_SENSITIVE)
+
+ @property
+ def showing(self):
+ return self.getState().contains(pyatspi.STATE_SHOWING)
+
+ @property
+ def focusable(self):
+ """Is the Accessible capable of having keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSABLE)
+
+ @property
+ def focused(self):
+ """Does the Accessible have keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSED)
+
+ @property
+ def checked(self):
+ """Is the Accessible a checked checkbox?"""
+ return self.getState().contains(pyatspi.STATE_CHECKED)
+
+ @property
+ def isChecked(self):
+ """Is the Accessible a checked checkbox? Compatibility property, same as Node.checked."""
+ return self.checked
+
+ #
+ # Selection
+ #
+
+ def selectAll(self):
+ """Selects all children."""
+ result = self.querySelection().selectAll()
+ doDelay()
+ return result
+
+ def deselectAll(self):
+ """Deselects all selected children."""
+ result = self.querySelection().clearSelection()
+ doDelay()
+ return result
+
+ def select(self):
+ """Selects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().selectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ def deselect(self):
+ """Deselects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().deselectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ @property
+ def isSelected(self):
+ """Is the Accessible selected? Compatibility property, same as Node.selected."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ return parent.querySelection().isChildSelected(self.indexInParent)
+
+ @property
+ def selected(self):
+ """Is the Accessible selected?"""
+ return self.isSelected
+
+ @property
+ def selectedChildren(self):
+ """Returns a list of children that are selected."""
+ # TODO: hideChildren for Hyperlinks?
+ selection = self.querySelection()
+ selectedChildren = []
+ for i in xrange(selection.nSelectedChildren):
+ selectedChildren.append(selection.getSelectedChild(i))
+
+ #
+ # Value
+ #
+
+ def value():
+ doc = "The value contained by the AccessibleValue interface."
+
+ def fget(self):
+ try:
+ return self.queryValue().currentValue
+ except NotImplementedError:
+ pass
+
+ def fset(self, value):
+ self.queryValue().currentValue = value
+
+ return property(**locals())
+ value = value()
+
+ @property
+ def minValue(self):
+ """The minimum value of self.value"""
+ try:
+ return self.queryValue().minimumValue
+ except NotImplementedError:
+ pass
+
+ @property
+ def minValueIncrement(self):
+ """The minimum value increment of self.value"""
+ try:
+ return self.queryValue().minimumIncrement
+ except NotImplementedError:
+ pass
+
+ @property
+ def maxValue(self):
+ """The maximum value of self.value"""
+ try:
+ return self.queryValue().maximumValue
+ except NotImplementedError:
+ pass
+
+ def typeText(self, string):
+ """
+ Type the given text into the node, with appropriate delays and
+ logging.
+ """
+ logger.log("Typing text into %s: '%s'" % (self.getLogString(), string))
+
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ rawinput.typeText(string)
+ else:
+ logger.log("Node is not focusable; falling back to inserting text")
+ et = self.queryEditableText()
+ et.insertText(self.caretOffset, string, len(string))
+ self.caretOffset += len(string)
+ doDelay()
+
+ def keyCombo(self, comboString):
+ if config.debugSearching:
+ logger.log("Pressing keys '%s' into %s" %
+ (comboString, self.getLogString()))
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ else:
+ logger.log("Node is not focusable; trying key combo anyway")
+ rawinput.keyCombo(comboString)
+
+ def getLogString(self):
+ """
+ Get a string describing this node for the logs,
+ respecting the config.absoluteNodePaths boolean.
+ """
+ if config.absoluteNodePaths:
+ return self.getAbsoluteSearchPath()
+ else:
+ return str(self)
+
+ def satisfies(self, pred):
+ """
+ Does this node satisfy the given predicate?
+ """
+ # the logic is handled by the predicate:
+ assert isinstance(pred, predicate.Predicate)
+ return pred.satisfiedByNode(self)
+
+ def dump(self, type='plain', fileName=None):
+ import dump
+ dumper = getattr(dump, type)
+ dumper(self, fileName)
+
+ def getAbsoluteSearchPath(self):
+ """
+ FIXME: this needs rewriting...
+ Generate a SearchPath instance giving the 'best'
+ way to find the Accessible wrapped by this node again, starting
+ at the root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem,
+ except that some of searches may be recursive, rather than just
+ searching direct children.
+
+ Used by the recording framework for identifying nodes in a
+ persistent way, independent of the style of script being
+ written.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+ if config.debugSearchPaths:
+ logger.log("getAbsoluteSearchPath(%s)" % self)
+
+ if self.roleName == 'application':
+ result = path.SearchPath()
+ result.append(predicate.IsAnApplicationNamed(self.name), False)
+ return result
+ else:
+ if self.parent:
+ (ancestor, pred, isRecursive) = self.getRelativeSearch()
+ if config.debugSearchPaths:
+ logger.log("got ancestor: %s" % ancestor)
+
+ ancestorPath = ancestor.getAbsoluteSearchPath()
+ ancestorPath.append(pred, isRecursive)
+ return ancestorPath
+ else:
+ # This should be the root node:
+ return path.SearchPath()
+
+ def getRelativeSearch(self):
+ """
+ Get a (ancestorNode, predicate, isRecursive) triple that identifies the
+ best way to find this Node uniquely.
+ FIXME: or None if no such search exists?
+ FIXME: may need to make this more robust
+ FIXME: should this be private?
+ """
+ if config.debugSearchPaths:
+ logger.log("getRelativeSearchPath(%s)" % self)
+
+ assert self
+ assert self.parent
+
+ isRecursive = False
+ ancestor = self.parent
+
+ # iterate up ancestors until you reach an identifiable one,
+ # setting the search to be isRecursive if need be:
+ while not self.__nodeIsIdentifiable(ancestor):
+ ancestor = ancestor.parent
+ isRecursive = True
+
+ # Pick the most appropriate predicate for finding this node:
+ if self.labellee:
+ if self.labellee.name:
+ return (ancestor, predicate.IsLabelledAs(self.labellee.name), isRecursive)
+
+ if self.roleName == 'menu':
+ return (ancestor, predicate.IsAMenuNamed(self.name), isRecursive)
+ elif self.roleName == 'menu item' or self.roleName == 'check menu item':
+ return (ancestor, predicate.IsAMenuItemNamed(self.name), isRecursive)
+ elif self.roleName == 'text':
+ return (ancestor, predicate.IsATextEntryNamed(self.name), isRecursive)
+ elif self.roleName == 'push button':
+ return (ancestor, predicate.IsAButtonNamed(self.name), isRecursive)
+ elif self.roleName == 'frame':
+ return (ancestor, predicate.IsAWindowNamed(self.name), isRecursive)
+ elif self.roleName == 'dialog':
+ return (ancestor, predicate.IsADialogNamed(self.name), isRecursive)
+ else:
+ pred = predicate.GenericPredicate(
+ name=self.name, roleName=self.roleName)
+ return (ancestor, pred, isRecursive)
+
+ def __nodeIsIdentifiable(self, ancestor):
+ if ancestor.labellee:
+ return True
+ elif ancestor.name:
+ return True
+ elif not ancestor.parent:
+ return True
+ else:
+ return False
+
+ def _fastFindChild(self, pred, recursive=True):
+ """
+ Searches for an Accessible using methods from pyatspi.utils
+ """
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None:
+ if pred(child):
+ return child
+ else:
+ return pyatspi.utils.findDescendant(self, pred)
+
+ def findChild(self, pred, recursive=True, debugName=None,
+ retry=True, requireResult=True):
+ """
+ Search for a node satisyfing the predicate, returning a Node.
+
+ If retry is True (the default), it makes multiple attempts,
+ backing off and retrying on failure, and eventually raises a
+ descriptive exception if the search fails.
+
+ If retry is False, it gives up after one attempt.
+
+ If requireResult is True (the default), an exception is raised after all
+ attempts have failed. If it is false, the function simply returns None.
+ """
+ def describeSearch(parent, pred, recursive, debugName):
+ """
+ Internal helper function
+ """
+ if recursive:
+ noun = "descendent"
+ else:
+ noun = "child"
+ if debugName is None:
+ debugName = pred.describeSearchResult()
+ return "%s of %s: %s" % (noun, parent.getLogString(), debugName)
+
+ assert isinstance(pred, predicate.Predicate)
+ numAttempts = 0
+ while numAttempts < config.searchCutoffCount:
+ if numAttempts >= config.searchWarningThreshold or config.debugSearching:
+ logger.log("searching for %s (attempt %i)" %
+ (describeSearch(self, pred, recursive, debugName), numAttempts))
+
+ result = self._fastFindChild(pred.satisfiedByNode, recursive)
+ if result:
+ assert isinstance(result, Node)
+ if debugName:
+ result.debugName = debugName
+ else:
+ result.debugName = pred.describeSearchResult()
+ return result
+ else:
+ if not retry:
+ break
+ numAttempts += 1
+ if config.debugSearching or config.debugSleep:
+ logger.log("sleeping for %f" %
+ config.searchBackoffDuration)
+ sleep(config.searchBackoffDuration)
+ if requireResult:
+ raise SearchError(describeSearch(self, pred, recursive, debugName))
+
+ # The canonical "search for multiple" method:
+ def findChildren(self, pred, recursive=True, isLambda=False):
+ """
+ Find all children/descendents satisfying the predicate.
+ """
+ if isLambda is True:
+ nodes = self.findChildren(predicate.GenericPredicate(), recursive=recursive)
+ result = []
+ for node in nodes:
+ try:
+ if pred(node):
+ result.append(node)
+ except:
+ pass
+ return result
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ result = []
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None and pred(child):
+ result.append(child)
+ return result
+ else:
+ descendants = []
+ while True:
+ try:
+ descendants = pyatspi.utils.findAllDescendants(self, pred)
+ break
+ except (GLib.GError, TypeError):
+ continue
+ return descendants
+
+ # The canonical "search above this node" method:
+ def findAncestor(self, pred):
+ """
+ Search up the ancestry of this node, returning the first Node
+ satisfying the predicate, or None.
+ """
+ assert isinstance(pred, predicate.Predicate)
+ candidate = self.parent
+ while candidate is not None:
+ if candidate.satisfies(pred):
+ return candidate
+ else:
+ candidate = candidate.parent
+ # Not found:
+ return None
+
+ # Various wrapper/helper search methods:
+ def child(self, name='', roleName='', description='', label='', recursive=True, retry=True, debugName=None):
+ """
+ Finds a child satisying the given criteria.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description, label=label), recursive=recursive, retry=retry, debugName=debugName)
+
+ def isChild(self, name='', roleName='', description='', label='', recursive=True, retry=False, debugName=None):
+ """
+ Determines whether a child satisying the given criteria exists.
+
+ This is implemented using findChild, but will not automatically retry
+ if no such child is found. To make the function retry multiple times set retry to True.
+ Returns a boolean value depending on whether the child was eventually found. Similar to
+ 'child', yet it catches SearchError exception to provide for False results, will raise
+ any other exceptions. It also logs the search.
+ """
+ found = True
+ try:
+ self.findChild(
+ predicate.GenericPredicate(
+ name=name, roleName=roleName, description=description, label=label),
+ recursive=recursive, retry=retry, debugName=debugName)
+ except SearchError:
+ found = False
+ return found
+
+ def menu(self, menuName, recursive=True):
+ """
+ Search below this node for a menu with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuNamed(menuName=menuName), recursive)
+
+ def menuItem(self, menuItemName, recursive=True):
+ """
+ Search below this node for a menu item with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuItemNamed(menuItemName=menuItemName), recursive)
+
+ def textentry(self, textEntryName, recursive=True):
+ """
+ Search below this node for a text entry with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATextEntryNamed(textEntryName=textEntryName), recursive)
+
+ def button(self, buttonName, recursive=True):
+ """
+ Search below this node for a button with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAButtonNamed(buttonName=buttonName), recursive)
+
+ def childLabelled(self, labelText, recursive=True):
+ """
+ Search below this node for a child labelled with the given text.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsLabelledAs(labelText), recursive)
+
+ def childNamed(self, childName, recursive=True):
+ """
+ Search below this node for a child with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsNamed(childName), recursive)
+
+ def tab(self, tabName, recursive=True):
+ """
+ Search below this node for a tab with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATabNamed(tabName=tabName), recursive)
+
+ def getUserVisibleStrings(self):
+ """
+ Get all user-visible strings in this node and its descendents.
+
+ (Could be implemented as an attribute)
+ """
+ result = []
+ if self.name:
+ result.append(self.name)
+ if self.description:
+ result.append(self.description)
+ try:
+ children = self.children
+ except Exception:
+ return result
+ for child in children:
+ result.extend(child.getUserVisibleStrings())
+ return result
+
+ def blink(self):
+ """
+ Blink, baby!
+ """
+ if not self.extents:
+ return False
+ else:
+ (x, y, w, h) = self.extents
+ Blinker(x, y, w, h)
+ return True
+
+
+class LinkAnchor(object):
+
+ """
+ Class storing info about an anchor within an Accessibility.Hyperlink, which
+ is in turn stored within an Accessibility.Hypertext.
+ """
+
+ def __init__(self, node, hypertext, linkIndex, anchorIndex):
+ self.node = node
+ self.hypertext = hypertext
+ self.linkIndex = linkIndex
+ self.anchorIndex = anchorIndex
+
+ @property
+ def link(self):
+ return self.hypertext.getLink(self.linkIndex)
+
+ @property
+ def URI(self):
+ return self.link.getURI(self.anchorIndex)
+
+
+class Root (Node):
+
+ """
+ FIXME:
+ """
+
+ def applications(self):
+ """
+ Get all applications.
+ """
+ return root.findChildren(predicate.GenericPredicate(
+ roleName="application"), recursive=False)
+
+ def application(self, appName, retry=True):
+ """
+ Gets an application by name, returning an Application instance
+ or raising an exception.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return root.findChild(predicate.IsAnApplicationNamed(appName), recursive=False, retry=retry)
+
+
+class Application (Node):
+
+ def dialog(self, dialogName, recursive=False):
+ """
+ Search below this node for a dialog with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: should this method activate the dialog?
+ """
+ return self.findChild(predicate.IsADialogNamed(dialogName=dialogName), recursive)
+
+ def window(self, windowName, recursive=False):
+ """
+ Search below this node for a window with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: this bit isn't true:
+ The window will be automatically activated (raised and focused
+ by the window manager) if wnck bindings are available.
+ """
+ result = self.findChild(
+ predicate.IsAWindowNamed(windowName=windowName), recursive)
+ # FIXME: activate the WnckWindow ?
+ # if gotWnck:
+ # result.activate()
+ return result
+
+ def getWnckApplication(self): # pragma: no cover
+ """
+ Get the wnck.Application instance for this application, or None
+
+ Currently implemented via a hack: requires the app to have a
+ window, and looks up the application of that window
+
+ wnck.Application can give you the pid, the icon, etc
+
+ FIXME: untested
+ """
+ window = self.child(roleName='frame')
+ if window:
+ wnckWindow = window.getWnckWindow()
+ return wnckWindow.get_application()
+
+
+class Window (Node):
+
+ def getWnckWindow(self): # pragma: no cover
+ """
+ Get the wnck.Window instance for this window, or None
+ """
+ # FIXME: this probably needs rewriting:
+ screen = Wnck.screen_get_default()
+
+ # You have to force an update before any of the wnck methods
+ # do anything:
+ screen.force_update()
+
+ for wnckWindow in screen.get_windows():
+ # FIXME: a dubious hack: search by window title:
+ if wnckWindow.get_name() == self.name:
+ return wnckWindow
+
+ def activate(self): # pragma: no cover
+ """
+ Activates the wnck.Window associated with this Window.
+
+ FIXME: doesn't yet work
+ """
+ wnckWindow = self.getWnckWindow()
+ # Activate it with a timestamp of 0; this may confuse
+ # alt-tabbing through windows etc:
+ # FIXME: is there a better way of getting a timestamp?
+ # gdk_x11_get_server_time (), with a dummy window
+ wnckWindow.activate(0)
+
+
+class Wizard (Window):
+
+ """
+ Note that the buttons of a GnomeDruid were not accessible until
+ recent versions of libgnomeui. This is
+ http://bugzilla.gnome.org/show_bug.cgi?id=157936
+ and is fixed in gnome-2.10 and gnome-2.12 (in CVS libgnomeui);
+ there's a patch attached to that bug.
+
+ This bug is known to affect FC3; fixed in FC5
+ """
+
+ def __init__(self, node, debugName=None):
+ Node.__init__(self, node)
+ if debugName:
+ self.debugName = debugName
+ logger.log("%s is on '%s' page" % (self, self.getPageTitle()))
+
+ def currentPage(self):
+ """
+ Get the current page of this wizard
+
+ FIXME: this is currently a hack, supporting only GnomeDruid
+ """
+ pageHolder = self.child(roleName='panel')
+ for child in pageHolder.children:
+ # current child has SHOWING state set, we hope:
+ # print child
+ # print child.showing
+ if child.showing:
+ return child
+ raise "Unable to determine current page of %s" % self
+
+ def getPageTitle(self):
+ """
+ Get the string title of the current page of this wizard
+
+ FIXME: this is currently a total hack, supporting only GnomeDruid
+ """
+ currentPage = self.currentPage()
+ return currentPage.child(roleName='panel').child(roleName='panel').child(roleName='label', recursive=False).text
+
+ def clickForward(self):
+ """
+ Click on the 'Forward' button to advance to next page of wizard.
+
+ It will log the title of the new page that is reached.
+
+ FIXME: what if it's Next rather than Forward ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Forward")
+ fwd.click()
+
+ # Log the new wizard page; it's helpful when debugging scripts
+ logger.log("%s is now on '%s' page" % (self, self.getPageTitle()))
+ # FIXME disabled for now (can't get valid page titles)
+
+ def clickApply(self):
+ """
+ Click on the 'Apply' button to advance to next page of wizard.
+ FIXME: what if it's Finish rather than Apply ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Apply")
+ fwd.click()
+
+ # FIXME: debug logging?
+
+Accessibility.Accessible.__bases__ = (
+ Application, Root, Node,) + Accessibility.Accessible.__bases__
+
+try:
+ root = pyatspi.Registry.getDesktop(0)
+ root.debugName = 'root'
+except Exception: # pragma: no cover
+ # Warn if AT-SPI's desktop object doesn't show up.
+ logger.log(
+ "Error: AT-SPI's desktop is not visible. Do you have accessibility enabled?")
+
+# Check that there are applications running. Warn if none are.
+children = root.children
+if not children: # pragma: no cover
+ logger.log(
+ "Warning: AT-SPI's desktop is visible but it has no children. Are you running any AT-SPI-aware applications?")
+del children
+
+import os
+# sniff also imports from tree and we don't want to run this code from
+# sniff itself
+if not os.path.exists('/tmp/sniff_running.lock'):
+ if not os.path.exists('/tmp/sniff_refresh.lock'): # may have already been locked by dogtail.procedural
+ # tell sniff not to use auto-refresh while script using this module is
+ # running
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError: # pragma: no cover
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+# Convenient place to set some debug variables:
+#config.debugSearching = True
+#config.absoluteNodePaths = True
+#config.logDebugToFile = False
diff --git a/build/lib.linux-x86_64-2.7/dogtail/utils.py b/build/lib.linux-x86_64-2.7/dogtail/utils.py
new file mode 100644
index 00000000000..be3c78adec2
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/utils.py
@@ -0,0 +1,395 @@
+# -*- coding: utf-8 -*-
+"""
+Various utilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+import os
+import sys
+import subprocess
+import cairo
+import predicate
+import errno
+import shlex
+
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from config import config
+from time import sleep
+from logging import debugLogger as logger
+from logging import TimeStamp
+from __builtin__ import file
+
+
+def screenshot(file='screenshot.png', timeStamp=True):
+ """
+ This function wraps the ImageMagick import command to take a screenshot.
+
+ The file argument may be specified as 'foo', 'foo.png', or using any other
+ extension that ImageMagick supports. PNG is the default.
+
+ By default, screenshot filenames are in the format of foo_YYYYMMDD-hhmmss.png .
+ The timeStamp argument may be set to False to name the file foo.png.
+ """
+ if not isinstance(timeStamp, bool):
+ raise TypeError("timeStampt must be True or False")
+ # config is supposed to create this for us. If it's not there, bail.
+ assert os.path.isdir(config.scratchDir)
+
+ baseName = ''.join(file.split('.')[0:-1])
+ fileExt = file.split('.')[-1].lower()
+ if not baseName:
+ baseName = file
+ fileExt = 'png'
+
+ if timeStamp:
+ ts = TimeStamp()
+ newFile = ts.fileStamp(baseName) + '.' + fileExt
+ path = config.scratchDir + newFile
+ else:
+ newFile = baseName + '.' + fileExt
+ path = config.scratchDir + newFile
+
+ from gi.repository import Gdk
+ from gi.repository import GLib
+ from gi.repository import GdkPixbuf
+ rootWindow = Gdk.get_default_root_window()
+ geometry = rootWindow.get_geometry()
+ pixbuf = GdkPixbuf.Pixbuf(colorspace=GdkPixbuf.Colorspace.RGB,
+ has_alpha=False,
+ bits_per_sample=8,
+ width=geometry[2],
+ height=geometry[3])
+
+ pixbuf = Gdk.pixbuf_get_from_window(rootWindow, 0, 0,
+ geometry[2], geometry[3])
+ # GdkPixbuf.Pixbuf.save() needs 'jpeg' and not 'jpg'
+ if fileExt == 'jpg':
+ fileExt = 'jpeg'
+ try:
+ pixbuf.savev(path, fileExt, [], [])
+ except GLib.GError:
+ raise ValueError("Failed to save screenshot in %s format" % fileExt)
+ assert os.path.exists(path)
+ logger.log("Screenshot taken: " + path)
+ return path
+
+
+def run(string, timeout=config.runTimeout, interval=config.runInterval, desktop=None, dumb=False, appName=''):
+ """
+ Runs an application. [For simple command execution such as 'rm *', use os.popen() or os.system()]
+ If dumb is omitted or is False, polls at interval seconds until the application is finished starting, or until timeout is reached.
+ If dumb is True, returns when timeout is reached.
+ """
+ if not desktop:
+ from tree import root as desktop
+ args = shlex.split(string)
+ os.environ['GTK_MODULES'] = 'gail:atk-bridge'
+ pid = subprocess.Popen(args, env=os.environ).pid
+
+ if not appName:
+ appName = args[0]
+
+ if dumb:
+ # We're starting a non-AT-SPI-aware application. Disable startup
+ # detection.
+ doDelay(timeout)
+ else:
+ # Startup detection code
+ # The timing here is not totally precise, but it's good enough for now.
+ time = 0
+ while time < timeout:
+ time = time + interval
+ try:
+ for child in desktop.children[::-1]:
+ if child.name == appName:
+ for grandchild in child.children:
+ if grandchild.roleName == 'frame':
+ from procedural import focus
+ focus.application.node = child
+ doDelay(interval)
+ return pid
+ except AttributeError: # pragma: no cover
+ pass
+ doDelay(interval)
+ return pid
+
+
+def doDelay(delay=None):
+ """
+ Utility function to insert a delay (with logging and a configurable
+ default delay)
+ """
+ if delay is None:
+ delay = config.defaultDelay
+ if config.debugSleep:
+ logger.log("sleeping for %f" % delay)
+ sleep(delay)
+
+
+class Highlight (Gtk.Window): # pragma: no cover
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ super(Highlight, self).__init__()
+ self.set_decorated(False)
+ self.set_has_resize_grip(False)
+ self.set_default_size(w, h)
+ self.screen = self.get_screen()
+ self.visual = self.screen.get_rgba_visual()
+ if self.visual is not None and self.screen.is_composited():
+ self.set_visual(self.visual)
+ self.set_app_paintable(True)
+ self.connect("draw", self.area_draw)
+ self.show_all()
+ self.move(x, y)
+
+ def area_draw(self, widget, cr): # pragma: no cover
+ cr.set_source_rgba(.0, .0, .0, 0.0)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
+ cr.set_source_rgb(0.9, 0.1, 0.1)
+ cr.set_line_width(6)
+ cr.rectangle(0, 0, self.get_size()[0], self.get_size()[1])
+ cr.stroke()
+
+
+class Blinker(object): # pragma: no cover
+ INTERVAL_MS = 1000
+ main_loop = GLib.MainLoop()
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ self.highlight_window = Highlight(x, y, w, h)
+ if self.highlight_window.screen.is_composited() is not False:
+ self.timeout_handler_id = GLib.timeout_add(
+ Blinker.INTERVAL_MS, self.destroyHighlight)
+ self.main_loop.run()
+ else:
+ self.highlight_window.destroy()
+
+ def destroyHighlight(self): # pragma: no cover
+ self.highlight_window.destroy()
+ self.main_loop.quit()
+ return False
+
+
+class Lock(object):
+
+ """
+ A mutex implementation that uses atomicity of the mkdir operation in UNIX-like
+ systems. This can be used by scripts to provide for mutual exlusion, either in single
+ scripts using threads etc. or i.e. to handle sitations of possible collisions among
+ multiple running scripts. You can choose to make randomized single-script wise locks
+ or a more general locks if you do not choose to randomize the lockdir name
+ """
+
+ def __init__(self, location='/tmp', lockname='dogtail_lockdir_', randomize=True):
+ """
+ You can change the default lockdir location or name. Setting randomize to
+ False will result in no random string being appened to the lockdir name.
+ """
+ self.lockdir = os.path.join(os.path.normpath(location), lockname)
+ if randomize:
+ self.lockdir = "%s%s" % (self.lockdir, self.__getPostfix())
+
+ def lock(self):
+ """
+ Creates a lockdir based on the settings on Lock() instance creation.
+ Raises OSError exception of the lock is already present. Should be
+ atomic on POSIX compliant systems.
+ """
+ locked_msg = 'Dogtail lock: Already locked with the same lock'
+ if not os.path.exists(self.lockdir):
+ try:
+ os.mkdir(self.lockdir)
+ return self.lockdir
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(self.lockdir):
+ raise OSError(locked_msg)
+ else:
+ raise OSError(locked_msg)
+
+ def unlock(self):
+ """
+ Removes a lock. Will raise OSError exception if the lock was not present.
+ Should be atomic on POSIX compliant systems.
+ """
+ import os # have to import here for situations when executed from __del__
+ if os.path.exists(self.lockdir):
+ try:
+ os.rmdir(self.lockdir)
+ except OSError as e:
+ if e.erron == errno.EEXIST:
+ raise OSError('Dogtail unlock: lockdir removed elsewhere!')
+ else:
+ raise OSError('Dogtail unlock: not locked')
+
+ def __del__(self):
+ """
+ Makes sure lock is removed when the process ends. Although not when killed indeed.
+ """
+ self.unlock()
+
+ def __getPostfix(self):
+ import random
+ import string
+ return ''.join(random.choice(string.letters + string.digits) for x in range(5))
+
+
+a11yDConfKey = 'org.gnome.desktop.interface'
+
+
+def isA11yEnabled():
+ """
+ Checks if accessibility is enabled via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(a11yDConfKey)
+ dconfEnabled = InterfaceSettings.get_boolean('toolkit-accessibility')
+ if os.environ.get('GTK_MODULES', '').find('gail:atk-bridge') == -1:
+ envEnabled = False
+ else:
+ envEnabled = True # pragma: no cover
+ return (dconfEnabled or envEnabled)
+
+
+def bailBecauseA11yIsDisabled():
+ if sys.argv[0].endswith("pydoc"):
+ return # pragma: no cover
+ try:
+ if file("/proc/%s/cmdline" % os.getpid()).read().find('epydoc') != -1:
+ return # pragma: no cover
+ except: # pragma: no cover
+ pass # pragma: no cover
+ logger.log("Dogtail requires that Assistive Technology support be enabled."
+ "\nYou can enable accessibility with sniff or by running:\n"
+ "'gsettings set org.gnome.desktop.interface toolkit-accessibility true'\nAborting...")
+ sys.exit(1)
+
+
+def enableA11y(enable=True):
+ """
+ Enables accessibility via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(schema=a11yDConfKey)
+ InterfaceSettings.set_boolean('toolkit-accessibility', enable)
+
+
+def checkForA11y():
+ """
+ Checks if accessibility is enabled, and halts execution if it is not.
+ """
+ if not isA11yEnabled(): # pragma: no cover
+ bailBecauseA11yIsDisabled()
+
+
+def checkForA11yInteractively(): # pragma: no cover
+ """
+ Checks if accessibility is enabled, and presents a dialog prompting the
+ user if it should be enabled if it is not already, then halts execution.
+ """
+ if isA11yEnabled():
+ return
+ from gi.repository import Gtk
+ dialog = Gtk.Dialog('Enable Assistive Technology Support?',
+ None,
+ Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ (Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE,
+ "_Enable", Gtk.ResponseType.ACCEPT))
+ question = """Dogtail requires that Assistive Technology Support be enabled for it to function. Would you like to enable Assistive Technology support now?
+
+Note that you will have to log out for the change to fully take effect.
+ """.strip()
+ dialog.set_default_response(Gtk.ResponseType.ACCEPT)
+ questionLabel = Gtk.Label(label=question)
+ questionLabel.set_line_wrap(True)
+ dialog.vbox.pack_start(questionLabel, True, True, 0)
+ dialog.show_all()
+ result = dialog.run()
+ if result == Gtk.ResponseType.ACCEPT:
+ logger.log("Enabling accessibility...")
+ enableA11y()
+ elif result == Gtk.ResponseType.CLOSE:
+ bailBecauseA11yIsDisabled()
+ dialog.destroy()
+
+
+class GnomeShell(object): # pragma: no cover
+
+ """
+ Utility class to help working with certain atributes of gnome-shell.
+ Currently that means handling the Application menu available for apps
+ on the top gnome-shell panel. Searching for the menu and its items is
+ somewhat tricky due to fuzzy a11y tree of gnome-shell, mainly since the
+ actual menu is not present as child to the menu-spawning button. Also,
+ the menus get constructed/destroyed on the fly with application focus
+ changes. Thus current application name as displayed plus a reference
+ known menu item (with 'Quit' as default) are required by these methods.
+ """
+
+ def __init__(self, classic_mode=False):
+ from tree import root
+ self.shell = root.application('gnome-shell')
+
+ def getApplicationMenuList(self, search_by_item='Quit'):
+ """
+ Returns list of all menu item nodes. Searches for the menu by a reference item.
+ Provide a different item name, if the 'Quit' is not present - but beware picking one
+ present elsewhere, like 'Lock' or 'Power Off' present under the user menu.
+ """
+ matches = self.shell.findChildren(
+ predicate.GenericPredicate(name=search_by_item, roleName='label'))
+ for match in matches:
+ ancestor = match.parent.parent.parent
+ if ancestor.roleName == 'panel':
+ return ancestor.findChildren(predicate.GenericPredicate(roleName='label'))
+ from tree import SearchError
+ raise SearchError("Could not find the Application menu based on '%s' item. Please provide an existing reference item"
+ % search_by_item)
+
+ def getApplicationMenuButton(self, app_name):
+ """
+ Returns the application menu 'button' node as present on the gnome-shell top panel.
+ """
+ try:
+ return self.shell[0][0][3].child(app_name, roleName='label')
+ except:
+ from tree import SearchError
+ raise SearchError(
+ "Application menu button of %s could not be found within gnome-shell!" % app_name)
+
+ def getApplicationMenuItem(self, item, search_by_item='Quit'):
+ """
+ Returns a particilar menu item node. Uses a different 'Quit' or custom item name for reference, but also
+ attempts to use the given item if the general reference fails.
+ """
+ try:
+ menu_items = self.getApplicationMenuList(search_by_item)
+ except:
+ menu_items = self.getApplicationMenuList(item)
+ for node in menu_items:
+ if node.name == item:
+ return node
+ raise Exception(
+ 'Could not find the item, did application focus change?')
+
+ def clickApplicationMenuItem(self, app_name, item, search_by_item='Quit'):
+ """
+ Executes the given menu item through opening the menu first followed
+ by a click at the particular item. The menu search reference 'Quit'
+ may be customized. Also attempts to use the given item for reference
+ if search fails with the default/custom one.
+ """
+ self.getApplicationMenuButton(app_name).click()
+ self.getApplicationMenuItem(item, search_by_item).click()
diff --git a/build/lib.linux-x86_64-2.7/dogtail/version.py b/build/lib.linux-x86_64-2.7/dogtail/version.py
new file mode 100644
index 00000000000..4d440806155
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/version.py
@@ -0,0 +1,56 @@
+"""Handles versioning of software packages
+
+Author: Dave Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'Dave Malcolm <dmalcolm@redhat.com>'
+
+
+class Version(object):
+
+ """
+ Class representing a version of a software package.
+ Stored internally as a list of subversions, from major to minor.
+ Overloaded comparison operators ought to work sanely.
+ """
+
+ def __init__(self, versionList):
+ self.versionList = versionList
+
+ def fromString(versionString):
+ """
+ Parse a string of the form number.number.number
+ """
+ return Version(map(int, versionString.split(".")))
+ fromString = staticmethod(fromString)
+
+ def __str__(self):
+ return ".".join(map(str, self.versionList))
+
+ def __getNum(self):
+ tmpList = list(self.versionList)
+
+ while len(tmpList) < 5:
+ tmpList += [0]
+
+ num = 0
+ for i in range(len(tmpList)):
+ num *= 1000
+ num += tmpList[i]
+ return num
+
+ def __lt__(self, other):
+ return self.__getNum() < other.__getNum()
+
+ def __le__(self, other):
+ return self.__getNum() <= other.__getNum()
+
+ def __eq__(self, other):
+ return self.__getNum() == other.__getNum()
+
+ def __ne__(self, other):
+ return self.__getNum() != other.__getNum()
+
+ def __gt__(self, other):
+ return self.__getNum() > other.__getNum()
+
+ def __ge__(self, other):
+ return self.__getNum() >= other.__getNum()
diff --git a/build/lib.linux-x86_64-2.7/dogtail/wrapped.py b/build/lib.linux-x86_64-2.7/dogtail/wrapped.py
new file mode 100644
index 00000000000..30c0570fa61
--- /dev/null
+++ b/build/lib.linux-x86_64-2.7/dogtail/wrapped.py
@@ -0,0 +1,33 @@
+"""
+Superclasses for application wrappers
+
+Subclass these classes if you want to create application wrappers, e.g.:
+http://svn.gnome.org/viewvc/dogtail-tests/trunk/appwrappers/dogtail/appwrappers/gedit.py?view=markup
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+import Accessibility
+
+
+def makeWrapperClass(wrappedClass, name): # pragma: no cover
+ class klass(object):
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def __getattr__(self, name):
+ if name == 'obj':
+ return self.__dict__['obj']
+ return getattr(self.obj, name)
+
+ def __setattr__(self, name, value):
+ if name == 'obj':
+ self.__dict__['obj'] = value
+ else:
+ return setattr(self.obj, name, value)
+
+ klass.__name__ = name
+ return klass
+
+Application = makeWrapperClass(Accessibility.Application,
+ "WrappedApplication")
+Node = makeWrapperClass(Accessibility.Accessible, "WrappedNode")
diff --git a/build/lib/dogtail/__init__.py b/build/lib/dogtail/__init__.py
new file mode 100644
index 00000000000..76a4a13fcb5
--- /dev/null
+++ b/build/lib/dogtail/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: UTF-8 -*-
+"""
+GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications.
+
+Authors: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vita Humpa <vhumpa@redhat.com>
+"""
+
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+Ed Rousseau <rousseau@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>,
+Vita Humpa <vhumpa@redhat.com>"""
+__version__ = "0.9.0"
+__copyright__ = "Copyright © 2005-2014 Red Hat, Inc."
+__license__ = "GPL"
+__all__ = ("config", "predicate",
+ "procedural", "tc", "tree", "utils", "errors")
diff --git a/build/lib/dogtail/config.py b/build/lib/dogtail/config.py
new file mode 100644
index 00000000000..82e197cf523
--- /dev/null
+++ b/build/lib/dogtail/config.py
@@ -0,0 +1,219 @@
+"""
+The configuration module.
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>"
+
+import os
+import sys
+import locale
+
+
+def _userTmpDir(baseName):
+ # i.e. /tmp/dogtail-foo
+ return '-'.join(('/'.join(('/tmp', baseName)), os.environ['USER']))
+
+
+class _Config(object):
+
+ """
+ Contains configuration parameters for the dogtail run.
+
+ scratchDir(str):
+ Directory where things like screenshots are stored.
+
+ dataDir(str):
+ Directory where related data files are located.
+
+ logDir(str):
+ Directory where dogtail.tc.TC*-generated logs are stored.
+
+ scriptName(str) [Read-Only]:
+ The name of the script being run.
+
+ encoding(str)
+ The encoding for text, used by dogtail.tc.TCString .
+
+ actionDelay(float):
+ The delay after an action is executed.
+
+ typingDelay(float):
+ The delay after a character is typed on the keyboard.
+
+ runInterval(float):
+ The interval at which dogtail.utils.run() and dogtail.procedural.run()
+ check to see if the application has started up.
+
+ runTimeout(int):
+ The timeout after which dogtail.utils.run() and dogtail.procedural.run()
+ give up on looking for the newly-started application.
+
+ searchBackoffDuration (float):
+ Time in seconds for which to delay when a search fails.
+
+ searchWarningThreshold (int):
+ Number of retries before logging the individual attempts at a search.
+
+ searchCutoffCount (int):
+ Number of times to retry when a search fails.
+
+ defaultDelay (float):
+ Default time in seconds to sleep when delaying.
+
+ childrenLimit (int):
+ When there are a very large number of children of a node, only return
+ this many, starting with the first.
+
+ debugSearching (boolean):
+ Whether to write info on search backoff and retry to the debug log.
+
+ debugSleep (boolean):
+ Whether to log whenever we sleep to the debug log.
+
+ debugSearchPaths (boolean):
+ Whether we should write out debug info when running the SearchPath
+ routines.
+
+ absoluteNodePaths (boolean):
+ Whether we should identify nodes in the logs with long 'abcolute paths', or
+ merely with a short 'relative path'. FIXME: give examples
+
+ ensureSensitivity (boolean):
+ Should we check that ui nodes are sensitive (not 'greyed out') before
+ performing actions on them? If this is True (the default) it will raise
+ an exception if this happens. Can set to False as a workaround for apps
+ and toolkits that don't report sensitivity properly.
+
+ debugTranslation (boolean):
+ Whether we should write out debug information from the translation/i18n
+ subsystem.
+
+ blinkOnActions (boolean):
+ Whether we should blink a rectangle around a Node when an action is
+ performed on it.
+
+ fatalErrors (boolean):
+ Whether errors encountered in dogtail.procedural should be considered
+ fatal. If True, exceptions will be raised. If False, warnings will be
+ passed to the debug logger.
+
+ checkForA11y (boolean):
+ Whether to check if accessibility is enabled. If not, just assume it is
+ (default True).
+
+ logDebugToFile (boolean):
+ Whether to write debug output to a log file.
+
+ logDebugToStdOut (boolean):
+ Whether to print log output to console or not (default True).
+ """
+ @property
+ def scriptName(self):
+ return os.path.basename(sys.argv[0]).replace('.py', '')
+
+ @property
+ def encoding(self):
+ return locale.getpreferredencoding().lower()
+
+ defaults = {
+ # Storage
+ 'scratchDir': '/'.join((_userTmpDir('dogtail'), '')),
+ 'dataDir': '/'.join((_userTmpDir('dogtail'), 'data', '')),
+ 'logDir': '/'.join((_userTmpDir('dogtail'), 'logs', '')),
+ 'scriptName': scriptName.fget(None),
+ 'encoding': encoding.fget(None),
+ 'configFile': None,
+ 'baseFile': None,
+
+ # Timing and Limits
+ 'actionDelay': 1.0,
+ 'typingDelay': 0.1,
+ 'runInterval': 0.5,
+ 'runTimeout': 30,
+ 'searchBackoffDuration': 0.5,
+ 'searchWarningThreshold': 3,
+ 'searchCutoffCount': 20,
+ 'defaultDelay': 0.5,
+ 'childrenLimit': 100,
+
+ # Debug
+ 'debugSearching': False,
+ 'debugSleep': False,
+ 'debugSearchPaths': False,
+ 'logDebugToStdOut': True,
+ 'absoluteNodePaths': False,
+ 'ensureSensitivity': False,
+ 'debugTranslation': False,
+ 'blinkOnActions': False,
+ 'fatalErrors': False,
+ 'checkForA11y': True,
+
+ # Logging
+ 'logDebugToFile': True
+ }
+
+ options = {}
+
+ invalidValue = "__INVALID__"
+
+ def __init__(self):
+ _Config.__createDir(_Config.defaults['scratchDir'])
+ _Config.__createDir(_Config.defaults['logDir'])
+ _Config.__createDir(_Config.defaults['dataDir'])
+
+ def __setattr__(self, name, value):
+ if name not in config.defaults:
+ raise AttributeError(name + " is not a valid option.")
+
+ elif _Config.defaults[name] != value or \
+ _Config.options.get(name, _Config.invalidValue) != value:
+ if 'Dir' in name:
+ _Config.__createDir(value)
+ if value[-1] != os.path.sep:
+ value = value + os.path.sep
+ elif name == 'logDebugToFile':
+ import logging
+ logging.debugLogger = logging.Logger('debug', value)
+ _Config.options[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return _Config.options[name]
+ except KeyError:
+ try:
+ return _Config.defaults[name]
+ except KeyError:
+ raise AttributeError("%s is not a valid option." % name)
+
+ def __createDir(cls, dirName, perms=0o777):
+ """
+ Creates a directory (if it doesn't currently exist), creating any
+ parent directories it needs.
+
+ If perms is None, create with python's default permissions.
+ """
+ dirName = os.path.abspath(dirName)
+ # print "Checking for %s ..." % dirName,
+ if not os.path.isdir(dirName):
+ if perms:
+ umask = os.umask(0)
+ os.makedirs(dirName, perms)
+ os.umask(umask)
+ else:
+ # This is probably a dead code - no other functions call this without the permissions set
+ os.makedirs(dirName) # pragma: no cover
+ __createDir = classmethod(__createDir)
+
+ def load(self, dict):
+ """
+ Loads values from dict, preserving any options already set that are not overridden.
+ """
+ _Config.options.update(dict)
+
+ def reset(self):
+ """
+ Resets all settings to their defaults.
+ """
+ _Config.options = {}
+
+
+config = _Config()
diff --git a/build/lib/dogtail/distro.py b/build/lib/dogtail/distro.py
new file mode 100644
index 00000000000..6f70f194650
--- /dev/null
+++ b/build/lib/dogtail/distro.py
@@ -0,0 +1,362 @@
+"""Handles differences between different distributions
+
+Authors: Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"
+
+import os
+import re
+from version import Version
+from logging import debugLogger as logger
+
+
+class DistributionNotSupportedError(Exception): # pragma: no cover
+
+ """
+ This distribution is not supported.
+ """
+ PATCH_MESSAGE = "Please send patches to dogtail-devel-list@gnome.org"
+
+ def __init__(self, distro):
+ self.distro = distro
+
+ def __str__(self):
+ return self.distro + ". " + DistributionNotSupportedError.PATCH_MESSAGE
+
+
+class PackageNotFoundError(Exception):
+
+ """
+ Error finding the requested package.
+ """
+ pass
+
+global packageDb
+global distro
+
+
+class PackageDb(object):
+
+ """
+ Class to abstract the details of whatever software package database is in
+ use (RPM, APT, etc)
+ """
+
+ def __init__(self):
+ self.prefix = '/usr'
+ self.localePrefixes = [self.prefix + '/share/locale']
+
+ def getVersion(self, packageName):
+ """
+ Method to get the version of an installed package as a Version
+ instance (or raise an exception if not found)
+
+ Note: does not know about distributions' internal revision numbers.
+ """
+ raise NotImplementedError
+
+ def getFiles(self, packageName):
+ """
+ Method to get a list of filenames owned by the package, or raise an
+ exception if not found.
+ """
+ raise NotImplementedError
+
+ def getMoFiles(self, locale=None):
+ """
+ Method to get a list of all .mo files on the system, optionally for a
+ specific locale.
+ """
+ moFiles = {}
+
+ def appendIfMoFile(moFiles, dirName, fNames):
+ import re
+ for fName in fNames:
+ if re.match('(.*)\\.mo', fName):
+ moFiles[dirName + '/' + fName] = None
+
+ for localePrefix in self.localePrefixes:
+ if locale:
+ localePrefix = localePrefix + '/' + locale
+ os.path.walk(localePrefix, appendIfMoFile, moFiles)
+
+ return moFiles.keys()
+
+ def getDependencies(self, packageName):
+ """
+ Method to get a list of unique package names that this package
+ is dependent on, or raise an exception if the package is not
+ found.
+ """
+ raise NotImplementedError
+
+
+class _RpmPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return Version.fromString(header["version"])
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return header["filenames"]
+ raise PackageNotFoundError(packageName)
+
+ def getDependencies(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+
+ # Get the list of requirements; these are
+ # sometimes package names, but can also be
+ # so-names of libraries, and invented virtual
+ # ids
+ for requirement in header[rpm.RPMTAG_REQUIRES]:
+ # Get the name of the package providing
+ # this requirement:
+ for depPackageHeader in ts.dbMatch("provides", requirement):
+ depName = depPackageHeader['name']
+ if depName != packageName:
+ # Add to the Hash with a dummy value
+ result[depName] = None
+ return result.keys()
+ raise PackageNotFoundError(packageName)
+
+
+class _AptPackageDb(PackageDb):
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ self.cache = None
+
+ def getVersion(self, packageName):
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ verString = re.match(
+ '.*Ver:\'(.*)-.*\' Section:', str(package.CurrentVer)).group(1)
+ return Version.fromString(verString)
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ files = []
+ list = os.popen('dpkg -L %s' % packageName).readlines()
+ if not list:
+ raise PackageNotFoundError(packageName)
+ else:
+ for line in list:
+ file = line.strip()
+ if file:
+ files.append(file)
+ return files
+
+ def getDependencies(self, packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ current = package.CurrentVer
+ if not current:
+ raise PackageNotFoundError(packageName)
+ depends = current.DependsList
+ list = depends['Depends']
+ for dependency in list:
+ name = dependency[0].TargetPkg.Name
+ # Add to the hash using a dummy value
+ result[name] = None
+ return result.keys()
+
+
+class _UbuntuAptPackageDb(_AptPackageDb):
+
+ def __init__(self):
+ _AptPackageDb.__init__(self)
+ self.localePrefixes.append(self.prefix + '/share/locale-langpack')
+
+
+class _PortagePackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ # the portage utilities are almost always going to be in
+ # /usr/lib/portage/pym
+ import sys
+ sys.path.append('/usr/lib/portage/pym')
+ import portage
+ # FIXME: this takes the first package returned in the list, in the
+ # case that there are slotted packages, and removes the leading
+ # category such as 'sys-apps'
+ gentooPackageName = portage.db["/"][
+ "vartree"].dbapi.match(packageName)[0].split('/')[1]
+ # this removes the distribution specific versioning returning only the
+ # upstream version
+ upstreamVersion = portage.pkgsplit(gentooPackageName)[1]
+ # print "Version of package is: " + upstreamVersion
+ return Version.fromString(upstreamVersion)
+
+
+class _ConaryPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ from conaryclient import ConaryClient
+ client = ConaryClient()
+ dbVersions = client.db.getTroveVersionList(packageName)
+ if not len(dbVersions):
+ raise PackageNotFoundError(packageName)
+ return dbVersions[0].trailingRevision().asString().split("-")[0]
+
+# getVersion not implemented because on Solaris multiple modules are installed
+# in single packages, so it is hard to tell what version number of a specific
+# module.
+
+
+class _SolarisPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+
+class JhBuildPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ prefixes = []
+ prefixes.append(os.environ['LD_LIBRARY_PATH'])
+ prefixes.append(os.environ['XDG_CONFIG_DIRS'])
+ prefixes.append(os.environ['PKG_CONFIG_PATH'])
+ self.prefix = os.path.commonprefix(prefixes)
+ self.localePrefixes.append(self.prefix + '/share/locale')
+
+ def getDependencies(self, packageName):
+ result = {}
+ lines = os.popen('jhbuild list ' + packageName).readlines()
+ for line in lines:
+ if line:
+ result[line.strip()] = None
+ return result.keys()
+
+
+class Distro(object):
+
+ """
+ Class representing a distribution.
+
+ Scripts may want to do arbitrary logic based on whichever distro is in use
+ (e.g. handling differences in names of packages, distribution-specific
+ patches, etc.)
+
+ We can either create methods in the Distro class to handle these, or we
+ can use constructs like isinstance(distro, Ubuntu) to handle this. We can
+ even create hierarchies of distro subclasses to handle this kind of thing
+ (could get messy fast though)
+ """
+
+
+class Fedora(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class RHEL(Fedora): # pragma: no cover
+ pass
+
+
+class Debian(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _AptPackageDb()
+
+
+class Ubuntu(Debian):
+
+ def __init__(self):
+ self.packageDb = _UbuntuAptPackageDb()
+
+
+class Suse(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class Gentoo(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _PortagePackageDb()
+
+
+class Conary(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _ConaryPackageDb()
+
+
+class Solaris(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _SolarisPackageDb()
+
+
+class JHBuild(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = JhBuildPackageDb()
+
+
+def detectDistro():
+ logger.log("Detecting distribution:", newline=False)
+
+ if os.environ.get("CERTIFIED_GNOMIE", "no") == "yes":
+ distro = JHBuild() # pragma: no cover
+ elif os.path.exists("/etc/SuSE-release"):
+ distro = Suse() # pragma: no cover
+ elif os.path.exists("/etc/fedora-release"):
+ distro = Fedora() # pragma: no cover
+ elif os.path.exists("/etc/redhat-release"):
+ distro = RHEL() # pragma: no cover
+ elif os.path.exists("/usr/share/doc/ubuntu-minimal"):
+ distro = Ubuntu()
+ elif os.path.exists("/etc/debian_version"): # pragma: no cover
+ distro = Debian() # pragma: no cover
+ elif os.path.exists("/etc/gentoo-release"): # pragma: no cover
+ distro = Gentoo() # pragma: no cover
+ elif os.path.exists("/etc/slackware-version"): # pragma: no cover
+ raise DistributionNotSupportedError("Slackware") # pragma: no cover
+ elif os.path.exists("/var/lib/conarydb/conarydb"): # pragma: no cover
+ distro = Conary() # pragma: no cover
+ elif os.path.exists("/etc/release") and \
+ re.match(".*Solaris", open("/etc/release").readline()): # pragma: no cover
+ distro = Solaris() # pragma: no cover
+ else:
+ raise DistributionNotSupportedError("Unknown") # pragma: no cover
+ logger.log(distro.__class__.__name__)
+ return distro
+
+distro = detectDistro()
+packageDb = distro.packageDb
diff --git a/build/lib/dogtail/dump.py b/build/lib/dogtail/dump.py
new file mode 100644
index 00000000000..3756820a510
--- /dev/null
+++ b/build/lib/dogtail/dump.py
@@ -0,0 +1,33 @@
+"""Utility functions for 'dumping' trees of Node objects.
+
+Author: Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from __builtin__ import file
+
+spacer = ' '
+
+
+def plain(node, fileName=None):
+ """
+ Plain-text dump. The hierarchy is represented through indentation.
+ """
+ def crawl(node, depth):
+ dump(node, depth)
+ for action in node.actions.values():
+ dump(action, depth + 1)
+ for child in node.children:
+ crawl(child, depth + 1)
+
+ def dumpFile(item, depth):
+ _file.write(spacer * depth + str(item) + '\n')
+
+ def dumpStdOut(item, depth):
+ print(spacer * depth + str(item))
+ if fileName:
+ dump = dumpFile
+ _file = file(fileName, 'w')
+ else:
+ dump = dumpStdOut
+
+ crawl(node, 0)
diff --git a/build/lib/dogtail/errors.py b/build/lib/dogtail/errors.py
new file mode 100644
index 00000000000..648af9d598e
--- /dev/null
+++ b/build/lib/dogtail/errors.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+General exceptions; not overly module-specific
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+from logging import debugLogger as logger
+
+import inspect
+
+
+def warn(message, caller=True):
+ """
+ Generate a warning, and pass it to the debug logger.
+ """
+ frameRec = inspect.stack()[-1]
+ message = "Warning: %s:%s: %s" % (frameRec[1], frameRec[2], message)
+ if caller and frameRec[1] != '<stdin>' and frameRec[1] != '<string>':
+ message = message + ':\n ' + frameRec[4][0]
+ del frameRec
+ logger.log(message)
+
+
+class DependencyNotFoundError(Exception):
+
+ """
+ A dependency was not found.
+ """
+ pass
diff --git a/build/lib/dogtail/i18n.py b/build/lib/dogtail/i18n.py
new file mode 100644
index 00000000000..8117f8d82c6
--- /dev/null
+++ b/build/lib/dogtail/i18n.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+"""
+Internationalization facilities
+
+Authors: David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+
+import config
+
+import os
+import re
+import gettext
+
+from logging import debugLogger as logger
+from __builtin__ import unicode
+
+
+def safeDecode(string):
+ try:
+ string = string.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ string = string.encode('utf-8', 'replace')
+ return string
+
+
+def safeEncode(string):
+ pass
+
+
+"""
+Singleton list of TranslationDb instances, to be initialized by the script with
+whatever translation databases it wants.
+"""
+translationDbs = []
+
+
+class TranslationDb(object):
+
+ """
+ Abstract base class representing a database of translations
+ """
+
+ def getTranslationsOf(self, srcName):
+ """
+ Pure virtual method to look up the translation of a string.
+ Returns a list of candidate strings (the translation), empty if not found.
+
+ Note that a source string can map to multiple translated strings. For
+ example, in the French translation of Evolution, the string "Forward" can
+ translate to both
+ (i) "Faire suivre" for forwarding an email, and
+ (ii) "Suivant" for the next page in a wizard.
+ """
+ raise NotImplementedError
+
+
+class GettextTranslationDb(TranslationDb):
+
+ """
+ Implementation of TranslationDb which leverages gettext, using a single
+ translation mo-file.
+ """
+
+ def __init__(self, moFile):
+ self.__moFile = moFile
+ self.__gnutranslations = gettext.GNUTranslations(open(moFile))
+
+ def getTranslationsOf(self, srcName):
+ srcName = safeDecode(srcName)
+ # print "searching for translations of %s"%srcName
+ # Use a dict to get uniqueness:
+ results = {}
+ result = self.__gnutranslations.ugettext(srcName)
+ if result != srcName:
+ results[result] = None
+
+ # Hack alert:
+ #
+ # Note that typical UI definition in GTK etc contains strings with
+ # underscores to denote accelerators.
+ # For example, the stock GTK "Add" item has text "_Add" which e.g.
+ # translates to "A_jouter" in French
+ #
+ # Since these underscores have been stripped out before we see these strings,
+ # we are looking for a translation of "Add" into "Ajouter" in this case, so
+ # we need to fake it, by looking up the string multiple times, with underscores
+ # inserted in all possible positions, stripping underscores out of the result.
+ # Ugly, but it works.
+
+ for index in range(len(srcName)):
+ candidate = srcName[:index] + "_" + srcName[index:]
+ result = self.__gnutranslations.ugettext(candidate)
+ if result != candidate:
+ # Strip out the underscore, and add to the result:
+ results[result.replace('_', '')] = True
+
+ return results.keys()
+
+
+def translate(srcString):
+ """
+ Look up srcString in the various translation databases (if any), returning
+ a list of all matches found (potentially the empty list)
+ """
+ # Use a dict to get uniqueness:
+ results = {}
+ # Try to translate the string:
+ for translationDb in translationDbs:
+ for result in translationDb.getTranslationsOf(srcString):
+ result = safeDecode(result)
+ results[result] = True
+
+ # No translations found:
+ if len(results) == 0:
+ if config.config.debugTranslation:
+ logger.log('Translation not found for "%s"' % srcString)
+ return results.keys()
+
+
+class TranslatableString(object):
+
+ """
+ Class representing a string that we want to match strings against, handling
+ translation for us, by looking it up once at construction time.
+ """
+
+ def __init__(self, untranslatedString):
+ """
+ Constructor looks up the string in all of the translation databases, storing
+ the various translations it finds.
+ """
+ untranslatedString = safeDecode(untranslatedString)
+ self.untranslatedString = untranslatedString
+ self.translatedStrings = translate(untranslatedString)
+
+ def matchedBy(self, string):
+ """
+ Compare the test string against either the translation of the original
+ string (or simply the original string, if no translation was found).
+ """
+ # print "comparing %s against %s"%(string, self)
+ def stringsMatch(inS, outS):
+ """
+ Compares a regular expression to a string
+
+ inS: the regular expression (or normal string)
+ outS: the normal string to be compared against
+ """
+ inString = str(inS)
+ outString = outS
+ if inString == outString:
+ return True
+ inString = inString + '$'
+ inString = safeDecode(inString)
+ outString = safeDecode(outString)
+ if inString[0] == '*':
+ inString = "\\" + inString
+ # Escape all parentheses, since grouping will never be needed here
+ inString = re.sub('([\(\)])', r'\\\1', inString)
+ match = re.match(inString, outString)
+ matched = match is not None
+ return matched
+
+ matched = False
+ # the 'ts' variable keeps track of whether we're working with
+ # translated strings. it's only used for debugging purposes.
+ #ts = 0
+ # print string, str(self)
+ for translatedString in self.translatedStrings:
+ #ts = ts + 1
+ matched = stringsMatch(translatedString, string)
+ if not matched:
+ matched = translatedString == string
+ if matched:
+ return matched
+ # ts=0
+ return stringsMatch(self.untranslatedString, string)
+
+ def __str__(self):
+ """
+ Provide a meaningful debug version of the string (and the translation in
+ use)
+ """
+ if len(self.translatedStrings) > 0:
+ # build an output string, with commas in the correct places
+ translations = ""
+ for tString in self.translatedStrings:
+ translations += u'"%s", ' % safeDecode(tString)
+ result = u'"%s" (%s)' % (
+ safeDecode(self.untranslatedString), translations)
+ return safeDecode(result)
+ else:
+ return '"%s"' % (self.untranslatedString)
+
+
+def isMoFile(filename, language=''):
+ """
+ Does the given filename look like a gettext mo file?
+
+ Optionally: Does the file also contain translations for a certain language,
+ for example 'ja'?
+ """
+ if re.match('(.*)\\.mo$', filename):
+ if not language:
+ return True
+ elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' %
+ language, filename):
+ return True
+ else:
+ return False
+ else:
+ return False
+
+
+def loadAllTranslationsForLanguage(language):
+ import distro
+ for moFile in distro.packageDb.getMoFiles(language):
+ translationDbs.append(GettextTranslationDb(moFile))
+
+
+def getMoFilesForPackage(packageName, language='', getDependencies=True):
+ """
+ Look up the named package and find all gettext mo files within it and its
+ dependencies. It is possible to restrict the results to those of a certain
+ language, for example 'ja'.
+ """
+ import distro
+
+ result = []
+ for filename in distro.packageDb.getFiles(packageName):
+ if isMoFile(filename, language):
+ result.append(filename)
+
+ if getDependencies:
+ # Recurse:
+ for dep in distro.packageDb.getDependencies(packageName):
+ # We pass False to the inner call because getDependencies has already
+ # walked the full tree
+ result.extend(getMoFilesForPackage(dep, language, False))
+
+ return result
+
+
+def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
+ """
+ Helper function which appends all of the gettext translation mo-files used by
+ the package (and its dependencies) to the translation database list.
+ """
+ # Keep a list of mo-files that are already in use to avoid duplicates.
+ moFiles = {}
+
+ def load(packageName, language='', getDependencies=True):
+ for moFile in getMoFilesForPackage(packageName, language, getDependencies):
+ # Searching the popt mo-files for translations makes gettext bail out,
+ # so we ignore them here. This is
+ # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 .
+ if not('popt.mo' in moFile or moFile in moFiles):
+ try:
+ translationDbs.append(GettextTranslationDb(moFile))
+ moFiles[moFile] = None
+ except (AttributeError, IndexError):
+ if config.config.debugTranslation:
+ #import traceback
+ # logger.log(traceback.format_exc())
+ logger.log(
+ "Warning: Failed to load mo-file for translation: " + moFile)
+
+ # Hack alert:
+ #
+ # The following special-case is necessary for Ubuntu, since their
+ # translations are shipped in a single huge package. The downside to
+ # this special case, aside from the simple fact that there is one,
+ # is that it makes automatic translations much slower.
+
+ import distro
+ language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2]
+ if isinstance(distro.distro, distro.Ubuntu):
+ load('language-pack-gnome-%s' % language, language)
+ load(packageName, language, getDependencies)
diff --git a/build/lib/dogtail/logging.py b/build/lib/dogtail/logging.py
new file mode 100644
index 00000000000..7e73f163b83
--- /dev/null
+++ b/build/lib/dogtail/logging.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+"""
+Logging facilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+import os
+import sys
+import time
+from config import config
+import codecs
+
+# Timestamp class for file logs
+
+
+class TimeStamp(object):
+
+ """
+ Generates timestamps tempfiles and log entries
+ """
+
+ def __init__(self):
+ self.now = "0"
+ self.timetup = time.localtime()
+
+ def zeroPad(self, int, width=2):
+ """
+ Pads an integer 'int' with zeroes, up to width 'width'.
+
+ Returns a string.
+
+ It will not truncate. If you call zeroPad(100, 2), '100' will be returned.
+ """
+ if int < 10 ** width:
+ return ("0" * (width - len(str(int)))) + str(int)
+ else:
+ return str(int)
+
+ # file stamper
+ def fileStamp(self, filename, addTime=True):
+ """
+ Generates a filename stamp in the format of filename_YYYYMMDD-hhmmss.
+ A format of filename_YYYYMMDD can be used instead by specifying addTime = False.
+ """
+ self.now = filename.strip() + "_"
+ self.timetup = time.localtime()
+
+ # Should produce rel-eng style filestamps
+ # format it all pretty by chopping the tuple
+ fieldCount = 3
+ if addTime:
+ fieldCount = fieldCount + 3
+ for i in range(fieldCount):
+ if i == 3:
+ self.now = self.now + '-'
+ self.now = self.now + self.zeroPad(self.timetup[i])
+ return self.now
+
+ # Log entry stamper
+ def entryStamp(self):
+ """
+ Generates a logfile entry stamp of YYYY.MM.DD HH:MM:SS
+ """
+ self.timetup = time.localtime()
+
+ # This will return a log entry formatted string in YYYY.MM.DD HH:MM:SS
+ for i in range(6):
+ # put in the year
+ if i == 0:
+ self.now = str(self.timetup[i])
+ # Format Month and Day
+ elif i == 1 or i == 2:
+ self.now = self.now + "." + self.zeroPad(self.timetup[i])
+ else:
+ # make the " " between Day and Hour and put in the hour
+ if i == 3:
+ self.now = self.now + " " + self.zeroPad(self.timetup[i])
+ # Otherwise Use the ":" divider
+ else:
+ self.now = self.now + ":" + self.zeroPad(self.timetup[i])
+ return self.now
+
+
+class Logger(object):
+
+ """
+ Writes entries to standard out.
+ """
+ stamper = TimeStamp()
+
+ def __init__(self, logName, file=False, stdOut=True):
+ """
+ name: the name of the log
+ file: The file object to log to.
+ stdOut: Whether to log to standard out.
+ """
+ self.logName = logName
+ self.stdOut = stdOut
+ self.file = file # Handle to the logfile
+ if not self.file:
+ return
+
+ scriptName = config.scriptName
+ if not scriptName:
+ scriptName = 'log'
+ self.fileName = scriptName
+
+ # check to see if we can write to the logDir
+ if os.path.isdir(config.logDir):
+ self.findUniqueName()
+ else:
+ # If path doesn't exist, raise an exception
+ raise IOError(
+ "Log path %s does not exist or is not a directory" % config.logDir)
+
+ def findUniqueName(self):
+ # generate a logfile name and check if it already exists
+ self.fileName = config.logDir + self.stamper.fileStamp(self.fileName) \
+ + '_' + self.logName
+ i = 0
+ while os.path.exists(self.fileName):
+ # Append the pathname
+ if i == 0:
+ self.fileName = self.fileName + "." + str(i)
+ else:
+ logsplit = self.fileName.split(".")
+ logsplit[-1] = str(i)
+ self.fileName = ".".join(logsplit)
+ i += 1
+
+ def createFile(self):
+ # Try to create the file and write the header info
+ print("Creating logfile at %s ..." % self.fileName)
+ self.file = codecs.open(self.fileName, mode='wb', encoding=
+ 'utf-8')
+ self.file.write("##### " + os.path.basename(self.fileName) + '\n')
+ self.file.flush()
+
+ def log(self, message, newline=True, force=False):
+ """
+ Hook used for logging messages. Might eventually be a virtual
+ function, but nice and simple for now.
+
+ If force is True, log to a file irrespective of config.logDebugToFile.
+ """
+ try:
+ message = message.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ pass
+
+
+ # Try to open and write the result to the log file.
+ if isinstance(self.file, bool) and (force or config.logDebugToFile):
+ self.createFile()
+
+ if force or config.logDebugToFile:
+ if newline:
+ self.file.write(message + '\n')
+ else:
+ self.file.write(message + ' ')
+ self.file.flush()
+
+ if self.stdOut and config.logDebugToStdOut:
+ if newline:
+ print(message)
+ else:
+ print(message)
+
+
+class ResultsLogger(Logger):
+
+ """
+ Writes entries into the Dogtail log
+ """
+
+ def __init__(self, stdOut=True):
+ Logger.__init__(self, 'results', file=True, stdOut=stdOut)
+
+ # Writes the result of a test case comparison to the log
+ def log(self, entry):
+ """
+ Writes the log entry. Requires a 1 {key: value} pair dict for an argument or else it will throw an exception.
+ """
+ # We require a 1 key: value dict
+ # Strip all leading and trailing witespace from entry dict and convert
+ # to string for writing
+
+ if len(entry) == 1:
+ key = entry.keys()
+ value = entry.values()
+ key = key[0]
+ value = value[0]
+ entry = str(key) + ": " + str(value)
+ else:
+ raise ValueError(entry)
+ print(
+ "Method argument requires a 1 {key: value} dict. Supplied argument not one {key: value}")
+
+ Logger.log(self, self.stamper.entryStamp() + " " + entry,
+ force=True)
+
+debugLogger = Logger('debug', config.logDebugToFile)
+
+import traceback
+
+
+def exceptionHook(exc, value, tb): # pragma: no cover
+ tbStringList = traceback.format_exception(exc, value, tb)
+ tbString = ''.join(tbStringList)
+ debugLogger.log(tbString)
+ sys.exc_clear()
+
+sys.excepthook = exceptionHook
diff --git a/build/lib/dogtail/path.py b/build/lib/dogtail/path.py
new file mode 100644
index 00000000000..5742cfb03e6
--- /dev/null
+++ b/build/lib/dogtail/path.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+Author: David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """David Malcolm <dmalcolm@redhat.com>"""
+
+
+class SearchPath(object):
+
+ """
+ Class used by the recording framework (and for more verbose script
+ logging) for identifying nodes in a persistent way, independent of the
+ style of script being written.
+
+ Implemented as a list of (predicate, isRecursive) pairs, giving the
+ 'best' way to find the Accessible wrapped by a Node, starting at the
+ root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem, except
+ that some of searches may be recursive, rather than just searching
+ direct children.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+
+ def __init__(self):
+ self.__list = []
+
+ def __str__(self):
+ result = "{"
+ for (predicate, isRecursive) in self.__list:
+ result += "/(%s,%s)" % (
+ predicate.describeSearchResult(), isRecursive)
+ return result + "}"
+
+ # We need equality to work so that dicts of these work:
+ def __eq__(self, other):
+ # print "eq: self:%s"%self
+ # print " other:%s"%other
+ if len(self.__list) != len(other.__list):
+ # print "nonequal length"
+ return False
+ else:
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ return False
+ # print True
+ return True
+
+ def append(self, predicate, isRecursive):
+ assert predicate
+ self.__list.append((predicate, isRecursive))
+
+ def __iter__(self):
+ return iter(self.__list)
+
+ def length(self):
+ return len(self.__list)
+
+ def makeScriptMethodCall(self):
+ """
+ Used by the recording system.
+
+ Generate the Python source code that will carry out this search.
+ """
+ result = ""
+ for (predicate, isRecursive) in self.__list:
+ # print predicate
+ # print self.generateVariableName(predicate)
+ result += "." + predicate.makeScriptMethodCall(isRecursive)
+ return result
+
+ def getRelativePath(self, other):
+ """
+ Given another SearchPath instance, if the other is 'below' this
+ one, return a SearchPath that describes how to reach it relative
+ to this one (a copy of the second part of the list). Otherwise
+ return None.
+ """
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ break
+ if i > 0:
+ # Slice from this point to the end:
+ result = SearchPath()
+ result.__list = other.__list[i + 1:]
+
+ if False:
+ print("....................")
+ print("from %s" % self)
+ print("to %s" % other)
+ print("i=%s" % i)
+ print("relative path %s" % result)
+ print("....................")
+
+ return result
+ else:
+ return None
+
+ def getPrefix(self, n):
+ """
+ Get the first n components of this instance as a new instance
+ """
+ result = SearchPath()
+ for i in range(n):
+ result.__list.append(self.__list[i])
+ return result
+
+ def getPredicate(self, i):
+ (predicate, isRecursive) = self.__list[i]
+ return predicate
diff --git a/build/lib/dogtail/predicate.py b/build/lib/dogtail/predicate.py
new file mode 100644
index 00000000000..aa100e1f054
--- /dev/null
+++ b/build/lib/dogtail/predicate.py
@@ -0,0 +1,443 @@
+"""Predicates that can be used when searching for nodes.
+
+Author: David Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'David Malcolm <dmalcolm@redhat.com>'
+
+from i18n import TranslatableString
+from gi.repository import GLib
+from time import sleep
+from logging import debugLogger as logger
+from config import config
+
+def stringMatches(scriptName, reportedName):
+ assert isinstance(scriptName, TranslatableString)
+
+ return scriptName.matchedBy(reportedName)
+
+
+def makeScriptRecursiveArgument(isRecursive, defaultValue):
+ if isRecursive == defaultValue:
+ return ""
+ else:
+ return ", recursive=%s" % isRecursive
+
+
+def makeCamel(string):
+ """
+ Convert string to camelCaps
+ """
+ string = str(string)
+ # FIXME: this function is probably really fragile, lots of difficult cases
+ # here
+
+ # Sanitize string, replacing bad characters with spaces:
+ for char in ":;!@#$%^&*()-+=_~`\\/?|[]{}<>,.\t\n\r\"'":
+ string = string.replace(char, " ")
+ words = string.strip().split(" ")
+ for word in words:
+ word.strip
+ result = ""
+ firstWord = True
+ for word in words:
+ lowercaseWord = word.lower()
+ if firstWord:
+ result += lowercaseWord
+ firstWord = False
+ else:
+ result += lowercaseWord.capitalize()
+ return result
+
+
+class Predicate(object):
+
+ """Abstract base class representing a predicate function on nodes.
+
+ It's more than just a function in that it has data and can describe itself"""
+
+ def satisfiedByNode(self, node):
+ """Pure virtual method returning a boolean if the predicate is satisfied by the node"""
+ raise NotImplementedError
+
+ def describeSearchResult(self, node):
+ raise NotImplementedError
+
+ def makeScriptMethodCall(self, isRecursive):
+ """
+ Method to generate a string containing a (hopefully) readable search
+ method call on a node (to be used when generating Python source code in
+ the event recorder)
+ """
+ raise NotImplementedError
+
+ def makeScriptVariableName(self):
+ """
+ Method to generate a string containing a (hopefully) readable name
+ for a Node instance variable that would be the result of a search on
+ this predicate (to be used when generating Python source code in the
+ event recorder).
+ """
+ raise NotImplementedError
+
+ def __eq__(self, other):
+ """
+ Predicates are considered equal if they are of the same subclass and
+ have the same data
+ """
+ # print "predeq: self:%s"%self
+ # print " other:%s"%other
+ # print "predeq: selfdict:%s"%self.__dict__
+ # print " otherdict:%s"%other.__dict__
+
+ if type(self) != type(other):
+ return False
+ else:
+ return self.__dict__ == other.__dict__
+
+
+class IsAnApplicationNamed(Predicate):
+
+ """Search subclass that looks for an application by name"""
+
+ def __init__(self, appName):
+ self.appName = TranslatableString(appName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ try:
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ try:
+ sleep(config.defaults['searchWarningThreshold'])
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError:
+ logger.log("Dogtail: warning: application may be hanging")
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s application' % self.appName
+
+ def makeScriptMethodCall(self, isRecursive):
+ # ignores the isRecursive parameter
+ return "application(%s)" % self.appName
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.appName) + "App"
+
+
+class GenericPredicate(Predicate):
+
+ """SubtreePredicate subclass that takes various optional search fields"""
+
+ def __init__(self, name=None, roleName=None, description=None, label=None, debugName=None):
+ if name:
+ self.name = TranslatableString(name)
+ else:
+ self.name = None
+ self.roleName = roleName
+ self.description = description
+ if label:
+ self.label = TranslatableString(label)
+ else:
+ self.label = None
+
+ if debugName:
+ self.debugName = debugName
+ else:
+ if label:
+ self.debugName = "labelled '%s'" % self.label
+ else:
+ self.debugName = "child with"
+ if name:
+ self.debugName += " name=%s" % self.name
+ if roleName:
+ self.debugName += " roleName='%s'" % roleName
+ if description:
+ self.debugName += " description='%s'" % description
+ assert self.debugName
+
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # labelled nodes are handled specially:
+ if self.label:
+ # this reverses the search; we're looking for a node with LABELLED_BY
+ # and then checking the label, rather than looking for a label and
+ # then returning whatever LABEL_FOR targets
+ if node.labeller:
+ return stringMatches(self.label, node.labeller.name)
+ else:
+ return False
+ else:
+ # Ensure the node matches any criteria that were set:
+ try:
+ if self.name:
+ if not stringMatches(self.name, node.name):
+ return False
+ if self.roleName:
+ if self.roleName != node.roleName:
+ return False
+ if self.description:
+ if self.description != node.description:
+ return False
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ raise e
+ return True
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return self.debugName
+
+ def makeScriptMethodCall(self, isRecursive):
+ if self.label:
+ args = "label=%s" % self.label
+ else:
+ args = ""
+ if self.name:
+ print(self.name)
+ args += " name=%s" % self.name
+ if self.roleName:
+ args += " roleName='%s'" % self.roleName
+ if self.description:
+ args += " description='%s'" % self.description
+ return "child(%s%s)" % (args, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ if self.label:
+ return makeCamel(self.label) + "Node"
+ else:
+ if self.name:
+ return makeCamel(self.name) + "Node"
+ if self.roleName:
+ return makeCamel(self.roleName) + "Node"
+ if self.description:
+ return makeCamel(self.description) + "Node"
+
+
+class IsNamed(Predicate):
+
+ """Predicate subclass that looks simply by name"""
+
+ def __init__(self, name):
+ self.name = TranslatableString(name)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return stringMatches(self.name, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "named %s" % self.name
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(name=%s%s)" % (self.name, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.name) + "Node"
+
+
+class IsAWindowNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level window by name"""
+
+ def __init__(self, windowName):
+ self.windowName = TranslatableString(windowName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'frame' and stringMatches(self.windowName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "%s window" % self.windowName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "window(%s%s)" % (self.windowName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.windowName) + "Win"
+
+
+class IsAWindow(Predicate):
+
+ """Predicate subclass that looks for top-level windows"""
+
+ def __init__(self):
+ self.satisfiedByNode = lambda node: node.roleName == 'frame'
+
+ def describeSearchResult(self):
+ return "window"
+
+
+class IsADialogNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level dialog by name"""
+
+ def __init__(self, dialogName):
+ self.dialogName = TranslatableString(dialogName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'dialog' and stringMatches(self.dialogName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s dialog' % self.dialogName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "dialog(%s%s)" % (self.dialogName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.dialogName) + "Dlg"
+
+
+class IsLabelledBy(Predicate):
+
+ """Predicate: is this node labelled by another node"""
+ pass
+
+
+class IsLabelledAs(Predicate):
+
+ """Predicate: is this node labelled with the text string (i.e. by another node with that as a name)"""
+
+ def __init__(self, labelText):
+ self.labelText = TranslatableString(labelText)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # FIXME
+ if node.labeller:
+ return stringMatches(self.labelText, node.labeller.name)
+ else:
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return 'labelled %s' % self.labelText
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(label=%s%s)" % (self.labelText, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.labelText) + "Node"
+
+
+class IsAMenuNamed(Predicate):
+
+ """Predicate subclass that looks for a menu by name"""
+
+ def __init__(self, menuName):
+ self.menuName = TranslatableString(menuName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'menu' and \
+ stringMatches(self.menuName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menu' % (self.menuName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menu(%s%s)" % (self.menuName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuName) + "Menu"
+
+
+class IsAMenuItemNamed(Predicate):
+
+ """Predicate subclass that looks for a menu item by name"""
+
+ def __init__(self, menuItemName):
+ self.menuItemName = TranslatableString(menuItemName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: \
+ node.roleName.endswith('menu item') and \
+ stringMatches(self.menuItemName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menuitem' % (self.menuItemName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menuItem(%s%s)" % (self.menuItemName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuItemName) + "MenuItem"
+
+
+class IsATextEntryNamed(Predicate):
+
+ """Predicate subclass that looks for a text entry by name"""
+
+ def __init__(self, textEntryName):
+ self.textEntryName = TranslatableString(textEntryName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'text' and \
+ stringMatches(self.textEntryName, node.name)
+
+ def describeSearchResult(self):
+ return '%s textentry' % (self.textEntryName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "textentry(%s%s)" % (self.textEntryName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.textEntryName) + "Entry"
+
+
+class IsAButtonNamed(Predicate):
+
+ """Predicate subclass that looks for a button by name"""
+
+ def __init__(self, buttonName):
+ self.buttonName = TranslatableString(buttonName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'push button' \
+ and stringMatches(self.buttonName, node.name)
+
+ def describeSearchResult(self):
+ return '%s button' % (self.buttonName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "button(%s%s)" % (self.buttonName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.buttonName) + "Button"
+
+
+class IsATabNamed(Predicate):
+
+ """Predicate subclass that looks for a tab by name"""
+
+ def __init__(self, tabName):
+ self.tabName = TranslatableString(tabName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'page tab' and \
+ stringMatches(self.tabName, node.name)
+
+ def describeSearchResult(self):
+ return '%s tab' % (self.tabName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "tab(%s%s)" % (self.tabName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.tabName) + "Tab"
diff --git a/build/lib/dogtail/procedural.py b/build/lib/dogtail/procedural.py
new file mode 100644
index 00000000000..c87ae86181c
--- /dev/null
+++ b/build/lib/dogtail/procedural.py
@@ -0,0 +1,455 @@
+"""
+Dogtail's procedural UI
+All the classes here are intended to be single-instance, except for Action.
+"""
+__author__ = 'Zack Cerza <zcerza@redhat.com>'
+#
+#
+# WARNING: Here There Be Dragons (TM) #
+#
+# If you don't understand how to use this API, you almost certainly don't #
+# want to read the code first. We make use of some very non-intuitive #
+# features of Python in order to make the API very simplistic. Therefore, #
+# you should probably only read this code if you're already familiar with #
+# some of Python's advanced features. You have been warned. ;) #
+#
+#
+
+import tree
+import predicate
+from config import config
+from utils import Lock
+import rawinput
+
+#FocusError = "FocusError: %s not found"
+
+
+class FocusError(Exception):
+ pass
+
+import errors
+
+
+def focusFailed(pred):
+ errors.warn('The requested widget could not be focused: %s' %
+ pred.debugName)
+
+ENOARGS = "At least one argument is needed"
+
+
+class FocusBase(object):
+
+ """
+ The base for every class in the module. Does nothing special, really.
+ """
+ node = None
+
+ def __getattr__(self, name):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ try:
+ return getattr(self.node, name)
+ except AttributeError:
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ if name == 'node':
+ setattr(self.__class__, name, value)
+ else:
+ try:
+ setattr(self.node, name, value)
+ except AttributeError:
+ raise AttributeError(name)
+
+
+class FocusApplication (FocusBase):
+
+ """
+ Keeps track of which application is currently focused.
+ """
+ desktop = tree.root
+
+ def __call__(self, name):
+ """
+ Search for an application that matches and refocus on the given name.
+ """
+ try:
+ pred = predicate.IsAnApplicationNamed(name)
+ app = self.desktop.findChild(
+ pred, recursive=False, retry=False)
+ except tree.SearchError:
+ if config.fatalErrors:
+ raise FocusError(name)
+ else:
+ focusFailed(pred)
+ return False
+ if app:
+ FocusApplication.node = app
+ FocusDialog.node = None
+ FocusWindow.node = None
+ FocusWidget.node = None
+ return True
+
+
+class FocusDesktop (FocusBase):
+
+ """
+ This isn't used yet, and may never be used.
+ """
+ pass
+
+
+class FocusWindow (FocusBase):
+
+ """
+ Keeps track of which window is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsAWindowNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWindow.node = result
+ FocusDialog.node = None
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusDialog (FocusBase):
+
+ """
+ Keeps track of which dialog is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsADialogNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusDialog.node = result
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusWidget (FocusBase):
+
+ """
+ Keeps track of which widget is currently focused.
+ """
+
+ def findByPredicate(self, pred):
+ result = None
+ try:
+ result = FocusWidget.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusDialog.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusWindow.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, retry=False)
+ if result:
+ FocusWidget.node = result
+ except AttributeError:
+ if config.fatalErrors:
+ raise FocusError(pred)
+ else:
+ focusFailed(pred)
+ return False
+
+ if result is None:
+ FocusWidget.node = result
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+ def __call__(self, name='', roleName='', description=''):
+ """
+ If name, roleName or description are specified, search for a widget that matches and refocus on it.
+ """
+ if not name and not roleName and not description:
+ raise TypeError(ENOARGS)
+
+ # search for a widget.
+ pred = predicate.GenericPredicate(name=name,
+ roleName=roleName, description=description)
+ return self.findByPredicate(pred)
+
+
+class Focus (FocusBase):
+
+ """
+ The container class for the focused application, dialog and widget.
+ """
+
+ def __getattr__(self, name):
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ if name in ('application', 'dialog', 'widget', 'window'):
+ self.__dict__[name] = value
+ else:
+ raise AttributeError(name)
+
+ desktop = tree.root
+ application = FocusApplication()
+ app = application # shortcut :)
+ dialog = FocusDialog()
+ window = FocusWindow()
+ frame = window
+ widget = FocusWidget()
+
+ def button(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+
+ def icon(self, name):
+ """
+ A shortcut to self.widget(name, roleName = 'icon')
+ """
+ return self.widget(name=name, roleName='icon')
+
+ def menu(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+
+ def menuItem(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+
+ def table(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table')
+ """
+ return self.widget(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table cell')
+ """
+ return self.widget(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self.widget.findByPredicate(IsATextEntryNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsATextEntryNamed(name))
+
+
+class Action (FocusWidget):
+
+ """
+ Aids in executing AT-SPI actions, refocusing the widget if necessary.
+ """
+
+ def __init__(self, action):
+ """
+ action is a string with the same name as the AT-SPI action you wish to execute using this class.
+ """
+ self.action = action
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ self.node.doActionNamed(self.action)
+
+ def __getattr__(self, attr):
+ return getattr(FocusWidget.node, attr)
+
+ def __setattr__(self, attr, value):
+ if attr == 'action':
+ self.__dict__[attr] = value
+ else:
+ setattr(FocusWidget, attr, value)
+
+ def button(self, name):
+ """
+ A shortcut to self(name, roleName = 'push button')
+ """
+ self.__call__(name=name, roleName='push button')
+
+ def menu(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu')
+ """
+ self.__call__(name=name, roleName='menu')
+
+ def menuItem(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu item')
+ """
+ self.__call__(name=name, roleName='menu item')
+
+ def table(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table')
+ """
+ self.__call__(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table cell')
+ """
+ self.__call__(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self(name, roleName = 'text')
+ """
+ self.__call__(name=name, roleName='text')
+
+
+class Click (Action):
+
+ """
+ A special case of Action, Click will eventually handle raw mouse events.
+ """
+ primary = 1
+ middle = 2
+ secondary = 3
+
+ def __init__(self):
+ Action.__init__(self, 'click')
+
+ def __call__(self, name='', roleName='', description='', raw=True, button=primary, delay=config.actionDelay):
+ """
+ By default, execute a raw mouse event.
+ If raw is False or if button evaluates to False, just pass the rest of
+ the arguments to Action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ if raw and button:
+ # We're doing a raw mouse click
+ Click.node.click(button)
+ else:
+ Action.__call__(
+ self, name=name, roleName=roleName, description=description, delay=delay)
+
+
+class Select (Action):
+
+ """
+ Aids in selecting and deselecting widgets, i.e. page tabs
+ """
+ select = 'select'
+ deselect = 'deselect'
+
+ def __init__(self, action):
+ """
+ action must be 'select' or 'deselect'.
+ """
+ if action not in (self.select, self.deselect):
+ raise ValueError(action)
+ Action.__init__(self, action)
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ func = getattr(self.node, self.action)
+ func()
+
+
+def type(text):
+ if focus.widget.node:
+ focus.widget.node.typeText(text)
+ else:
+ rawinput.typeText(text)
+
+
+def keyCombo(combo):
+ if focus.widget.node:
+ focus.widget.node.keyCombo(combo)
+ else:
+ rawinput.keyCombo(combo)
+
+
+def run(application, arguments='', appName=''):
+ from utils import run as utilsRun
+ pid = utilsRun(application + ' ' + arguments, appName=appName)
+ focus.application(application)
+ return pid
+
+import os
+# tell sniff not to use auto-refresh while script using this module is running
+# may have already been locked by dogtail.tree
+if not os.path.exists('/tmp/sniff_refresh.lock'): # pragma: no cover
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError:
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+focus = Focus()
+click = Click()
+activate = Action('activate')
+openItem = Action('open')
+menu = Action('menu')
+select = Select(Select.select)
+deselect = Select(Select.deselect)
diff --git a/build/lib/dogtail/rawinput.py b/build/lib/dogtail/rawinput.py
new file mode 100644
index 00000000000..7100b29bfae
--- /dev/null
+++ b/build/lib/dogtail/rawinput.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+"""
+Handles raw input using AT-SPI event generation.
+
+Note: Think of keyvals as keysyms, and keynames as keystrings.
+
+Authors: David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>
+"""
+
+__author__ = """
+David Malcolm <dmalcolm@redhat.com>,
+Zack Cerza <zcerza@redhat.com>
+"""
+import gi
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gdk
+from config import config
+from utils import doDelay
+from logging import debugLogger as logger
+from pyatspi import Registry as registry
+from pyatspi import (KEY_SYM, KEY_PRESS, KEY_PRESSRELEASE, KEY_RELEASE)
+from exceptions import ValueError
+from __builtin__ import unicode, unichr
+
+
+def doTypingDelay():
+ doDelay(config.typingDelay)
+
+
+def checkCoordinates(x, y):
+ if x < 0 or y < 0:
+ raise ValueError(
+ "Attempting to generate a mouse event at negative coordinates: (%s,%s)" % (x, y))
+
+
+def click(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s click at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sc' % button)
+ doDelay(config.actionDelay)
+
+
+def doubleClick(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button double-click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s doubleclick at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sd' % button)
+ doDelay()
+
+
+def press(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button press at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s press at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sp' % button)
+ doDelay()
+
+
+def release(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button release at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s release at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sr' % button)
+ doDelay()
+
+
+def absoluteMotion(x, y, mouseDelay=None, check=True):
+ """
+ Synthesize mouse absolute motion to (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse absolute motion to (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def relativeMotion(x, y, mouseDelay=None):
+ logger.log("Mouse relative motion of (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'rel')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def drag(fromXY, toXY, button=1, check=True):
+ """
+ Synthesize a mouse press, drag, and release on the screen.
+ """
+ logger.log("Mouse button %s drag from %s to %s" % (button, fromXY, toXY))
+
+ (x, y) = fromXY
+ press(x, y, button, check)
+ # doDelay()
+
+ (x, y) = toXY
+ absoluteMotion(x, y, check=check)
+ doDelay()
+
+ release(x, y, button, check)
+ doDelay()
+
+
+def typeText(string):
+ """
+ Types the specified string, one character at a time.
+ Please note, you may have to set a higher typing delay,
+ if your machine misses/switches the characters typed.
+ Needed sometimes on slow setups/VMs typing non-ASCII utf8 chars.
+ """
+ if not isinstance(string, unicode):
+ string = string.decode('utf-8')
+ for char in string:
+ pressKey(char)
+
+keyNameAliases = {
+ 'enter': 'Return',
+ 'esc': 'Escape',
+ 'alt': 'Alt_L',
+ 'control': 'Control_L',
+ 'ctrl': 'Control_L',
+ 'shift': 'Shift_L',
+ 'del': 'Delete',
+ 'ins': 'Insert',
+ 'pageup': 'Page_Up',
+ 'pagedown': 'Page_Down',
+ ' ': 'space',
+ '\t': 'Tab',
+ '\n': 'Return'
+}
+
+
+# TODO: Dead code
+def keySymToUniChar(keySym): # pragma: no cover
+ i = Gdk.keyval_to_unicode(keySym)
+ if i:
+ UniChar = unichr(i)
+ else:
+ UniChar = ''
+ return UniChar
+
+
+def uniCharToKeySym(uniChar):
+ # OK, if it's not actually unicode we can fix that, right?
+ if not isinstance(uniChar, unicode):
+ uniChar = unicode(uniChar, 'utf-8')
+ i = ord(uniChar)
+ keySym = Gdk.unicode_to_keyval(i)
+ return keySym
+
+
+# dead code
+def keySymToKeyName(keySym): # pragma: no cover
+ return Gdk.keyval_name(keySym)
+
+
+def keyNameToKeySym(keyName):
+ keyName = keyNameAliases.get(keyName.lower(), keyName)
+ keySym = Gdk.keyval_from_name(keyName)
+ # various error 'codes' returned for non-recognized chars in versions of GTK3.X
+ if keySym == 0xffffff or keySym == 0x0 or keySym is None:
+ try:
+ keySym = uniCharToKeySym(keyName)
+ except: # not even valid utf-8 char
+ try: # Last attempt run at a keyName ('Meta_L', 'Dash' ...)
+ keySym = getattr(Gdk, 'KEY_' + keyName)
+ except AttributeError:
+ raise KeyError(keyName)
+ return keySym
+
+
+def keyNameToKeyCode(keyName):
+ """
+ Use GDK to get the keycode for a given keystring.
+
+ Note that the keycode returned by this function is often incorrect when
+ the requested keystring is obtained by holding down the Shift key.
+
+ Generally you should use uniCharToKeySym() and should only need this
+ function for nonprintable keys anyway.
+ """
+ keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default())
+ entries = keymap.get_entries_for_keyval(
+ Gdk.keyval_from_name(keyName))
+ try:
+ return entries[1][0].keycode
+ except TypeError:
+ pass
+
+
+def pressKey(keyName):
+ """
+ Presses (and releases) the key specified by keyName.
+ keyName is the English name of the key as seen on the keyboard. Ex: 'enter'
+ Names are looked up in Gdk.KEY_ If they are not found there, they are
+ looked up by uniCharToKeySym().
+ """
+ keySym = keyNameToKeySym(keyName)
+ registry.generateKeyboardEvent(keySym, None, KEY_SYM)
+ doTypingDelay()
+
+
+def keyCombo(comboString):
+ """
+ Generates the appropriate keyboard events to simulate a user pressing the
+ specified key combination.
+
+ comboString is the representation of the key combo to be generated.
+ e.g. '<Control><Alt>p' or '<Control><Shift>PageUp' or '<Control>q'
+ """
+ strings = []
+ for s in comboString.split('<'):
+ if s:
+ for S in s.split('>'):
+ if S:
+ S = keyNameAliases.get(S.lower(), S)
+ strings.append(S)
+ for s in strings:
+ if not hasattr(Gdk, s):
+ if not hasattr(Gdk, 'KEY_' + s):
+ raise ValueError("Cannot find key %s" % s)
+ modifiers = strings[:-1]
+ finalKey = strings[-1]
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_PRESS)
+ code = keyNameToKeyCode(finalKey)
+ registry.generateKeyboardEvent(code, None, KEY_PRESSRELEASE)
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_RELEASE)
+ doDelay()
diff --git a/build/lib/dogtail/sessions.py b/build/lib/dogtail/sessions.py
new file mode 100644
index 00000000000..8d4bcce6a37
--- /dev/null
+++ b/build/lib/dogtail/sessions.py
@@ -0,0 +1,231 @@
+import time
+import os
+import pwd
+import errno
+import re
+import subprocess
+import signal
+import tempfile
+import random
+import glob
+from dogtail.config import config
+
+
+def scratchFile(label): # pragma: no cover
+ """Uses tempfile.NamedTemporaryFile() to create a unique tempfile in
+ config.scratchDir, with a filename like:
+ dogtail-headless-<label>.<random junk>"""
+ prefix = "dogtail-headless-"
+ return tempfile.NamedTemporaryFile(prefix="%s%s." % (prefix, label),
+ dir=config.scratchDir)
+
+
+def testBinary(path): # pragma: no cover
+ if (path.startswith(os.path.sep) or
+ path.startswith(os.path.join('.', '')) or
+ path.startswith(os.path.join('..', ''))):
+ if not os.path.exists(path):
+ raise IOError(errno.ENOENT, "No such file", path)
+ if not os.access(path, os.X_OK):
+ raise IOError(errno.ENOEXEC, "Permission denied", path)
+ return True
+
+
+def get_username(): # pragma: no cover
+ return pwd.getpwuid(os.getuid())[0]
+
+
+class Subprocess(object): # pragma: no cover
+
+ def __init__(self, cmdList, environ=None):
+ testBinary(cmdList[0])
+ self.cmdList = cmdList
+ self.environ = environ
+ self._exitCode = None
+
+ def start(self):
+ if self.environ is None:
+ self.environ = os.environ
+ self.popen = subprocess.Popen(
+ self.cmdList, env=self.environ) # , stdout = subprocess.PIPE,
+ # stderr = subprocess.STDOUT, close_fds = True)
+ return self.popen.pid
+
+ def wait(self):
+ return self.popen.wait()
+
+ def stop(self):
+ # The following doesn't exist in python < 2.6, if you can believe it.
+ # self.popen.terminate()
+ os.kill(self.popen.pid, signal.SIGTERM)
+
+ @property
+ def exitCode(self):
+ if self._exitCode is None:
+ self._exitCode = self.wait()
+ return self._exitCode
+
+
+class XServer(Subprocess): # pragma: no cover
+
+ def __init__(self, server="/usr/bin/Xorg",
+ xinitrc="/etc/X11/xinit/Xclients",
+ resolution="1024x768x16"):
+ """resolution is only used with Xvfb."""
+ testBinary(server)
+ self.server = server
+ self._exitCode = None
+ self.xinit = "/usr/bin/xinit"
+ self.display = None
+ self.xinitrc = xinitrc
+ self.resolution = resolution
+
+ @staticmethod
+ def findFreeDisplay():
+ tmp = os.listdir('/tmp')
+ pattern = re.compile('\.X([0-9]+)-lock')
+ usedDisplays = []
+ for file in tmp:
+ match = re.match(pattern, file)
+ if match:
+ usedDisplays.append(int(match.groups()[0]))
+ if not usedDisplays:
+ return ':0'
+ usedDisplays.sort()
+ return ':' + str(usedDisplays[-1] + 1)
+
+ @property
+ def cmdList(self):
+ self.display = self.findFreeDisplay()
+ cmd = []
+ if self.xinit:
+ cmd.append(self.xinit)
+ if self.xinitrc:
+ cmd.append(self.xinitrc)
+ cmd.append('--')
+ cmd.append(self.server)
+ cmd.append(self.display)
+ cmd.extend(['-ac', '-noreset'])
+ if self.server.endswith('Xvfb'):
+ cmd.extend(['-screen', '0', self.resolution])
+ cmd.append('-shmem')
+ return cmd
+
+ def start(self):
+ print(' '.join(self.cmdList))
+ self.popen = subprocess.Popen(self.cmdList)
+ return self.popen.pid
+
+
+class Script(Subprocess): # pragma: no cover
+ pass
+
+
+class Session(object): # pragma: no cover
+
+ cookieName = "DOGTAIL_SESSION_COOKIE"
+
+ def __init__(self, sessionBinary, scriptCmdList=[], scriptDelay=20, logout=True):
+ testBinary(sessionBinary)
+ self.sessionBinary = sessionBinary
+ self.script = Script(scriptCmdList)
+ self.scriptDelay = scriptDelay
+ self.logout = logout
+ self.xserver = XServer()
+ self._cookie = None
+ self._environment = None
+
+ def start(self):
+ self.xinitrcFileObj = scratchFile('xinitrc')
+ self.xserver.xinitrc = self.xinitrcFileObj.name
+ self._buildXInitRC(self.xinitrcFileObj)
+ xServerPid = self.xserver.start()
+ time.sleep(self.scriptDelay)
+ self.script.environ = self.environment
+ scriptPid = self.script.start()
+ return (xServerPid, scriptPid)
+
+ @property
+ def environment(self):
+ def isSessionProcess(fileName):
+ try:
+ if os.path.realpath(path + 'exe') != ('/usr/bin/plasma-desktop'
+ if self.sessionBinary.split('/')[-1] == 'startkde'
+ else self.sessionBinary):
+ return False
+ except OSError:
+ return False
+ pid = fileName.split('/')[2]
+ if pid == 'self' or pid == str(os.getpid()):
+ return False
+ return True
+
+ def getEnvDict(fileName):
+ try:
+ envString = open(fileName, 'r').read()
+ except IOError:
+ return {}
+ envItems = envString.split('\x00')
+ envDict = {}
+ for item in envItems:
+ if not '=' in item:
+ continue
+ k, v = item.split('=', 1)
+ envDict[k] = v
+ return envDict
+
+ def isSessionEnv(envDict):
+ if not envDict:
+ return False
+ if envDict.get(self.cookieName, 'notacookie') == self.cookie:
+ return True
+ return False
+
+ for path in glob.glob('/proc/*/'):
+ if not isSessionProcess(path):
+ continue
+ envFile = path + 'environ'
+ envDict = getEnvDict(envFile)
+ if isSessionEnv(envDict):
+ # print path
+ # print envDict
+ self._environment = envDict
+ if not self._environment:
+ raise RuntimeError("Can't find our environment!")
+ return self._environment
+
+ def wait(self):
+ self.script.wait()
+ return self.xserver.wait()
+
+ def stop(self):
+ try:
+ self.script.stop()
+ except OSError:
+ pass
+ self.xserver.stop()
+
+ def attemptLogout(self):
+ logoutScript = Script('dogtail-logout',
+ environ=self.environment)
+ logoutScript.start()
+ logoutScript.wait()
+
+ @property
+ def cookie(self):
+ if not self._cookie:
+ self._cookie = "%X" % random.getrandbits(16)
+ return self._cookie
+
+ def _buildXInitRC(self, fileObj):
+ lines = [
+ "export %s=%s" % (self.cookieName, self.cookie),
+ "gsettings set org.gnome.desktop.interface toolkit-accessibility true",
+ ". /etc/X11/xinit/xinitrc-common",
+ "export %s" % self.cookieName,
+ "exec -l $SHELL -c \"$CK_XINIT_SESSION $SSH_AGENT %s\"" %
+ (self.sessionBinary),
+ ""]
+
+ fileObj.write('\n'.join(lines).strip())
+ fileObj.flush()
diff --git a/build/lib/dogtail/tc.py b/build/lib/dogtail/tc.py
new file mode 100644
index 00000000000..c7ef9f5bc23
--- /dev/null
+++ b/build/lib/dogtail/tc.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+"""Test Case magic
+
+Author: Ed Rousseau <rousseau@redhat.com>"""
+__author__ = "Ed Rousseau <rousseau@redhat.com>"
+
+import os
+import os.path
+from config import config
+from logging import ResultsLogger, TimeStamp
+from PIL import Image, ImageChops, ImageStat
+from __builtin__ import unicode, long
+
+
+class TC(object): # pragma: no cover
+
+ """
+ The Test Case Superclass
+ """
+ logger = ResultsLogger()
+
+ def __init__(self):
+ self.encoding = config.encoding
+ # ascii + unicode. 8 bit extended char has been ripped out
+ self.supportedtypes = (
+ "ascii", "utf-8", "utf-16", "utf-16-be", "utf-16-le", "unicode-escape", "raw-unicode-escape",
+ "big5", "gb18030", "eucJP", "eucKR", "shiftJIS")
+
+ # String comparison function
+ def compare(self, label, baseline, undertest, encoding=config.encoding):
+ """
+ Compares 2 strings to see if they are the same. The user may specify
+ the encoding to which the two strings are to be normalized for the
+ comparison. Default encoding is the default system encoding.
+ Normalization to extended 8 bit charactersets is not supported.
+
+ When the origin of either baseline or undertest is a text file whose
+ encoding is something other than ASCII, it is necessary to use
+ codecs.open() instead of open(), so the file's encoding may be
+ specified.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ for string in [self.baseline, self.undertest]:
+ try:
+ string = unicode(string, 'utf-8')
+ except TypeError:
+ pass
+ self.encoding = encoding
+
+ # Normalize the encoding type for the comparaison based on
+ # self.encoding
+ if self.encoding in self.supportedtypes:
+ self.baseline = (self.baseline).encode(self.encoding)
+ self.undertest = (self.undertest).encode(self.encoding)
+ # Compare the strings
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed"}
+ else:
+ self.result = {self.label: "Failed - " + self.encoding +
+ " strings do not match. " + self.baseline + " expected: Got " + self.undertest}
+ # Pass the test result to the ResultsLogger for writing
+ TC.logger.log(self.result)
+ return self.result
+
+ else:
+ # We should probably raise an exception here
+ self.result = {
+ self.label: "ERROR - " + self.encoding + " is not a supported encoding type"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+# String Test Case subclass
+class TCString(TC): # pragma: no cover
+
+ """
+ String Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+
+# Image test case subclass
+
+
+class TCImage(TC): # pragma: no cover
+
+ """
+ Image Test Case Class.
+ """
+
+ def compare(self, label, baseline, undertest):
+ for _file in (baseline, undertest):
+ if type(_file) is not unicode and type(_file) is not str:
+ raise TypeError("Need filenames!")
+ self.label = label.strip()
+ self.baseline = baseline.strip()
+ self.undertest = undertest.strip()
+ diffName = TimeStamp().fileStamp("diff") + ".png"
+ self.diff = os.path.normpath(
+ os.path.sep.join((config.scratchDir, diffName)))
+
+ self.baseImage = Image.open(self.baseline)
+ self.testImage = Image.open(self.undertest)
+ try:
+ if self.baseImage.size != self.testImage.size:
+ self.result = {
+ self.label: "Failed - images are different sizes"}
+ raise StopIteration
+
+ self.diffImage = ImageChops.difference(self.baseImage,
+ self.testImage)
+ self.diffImage.save(self.diff)
+ result = False
+ for stat in ('stddev', 'mean', 'sum2'):
+ for item in getattr(ImageStat.Stat(self.diffImage), stat):
+ if item:
+ self.result = {self.label: "Failed - see %s" %
+ self.diff}
+ raise StopIteration
+ else:
+ result = True
+ except StopIteration:
+ result = False
+
+ if result:
+ self.result = {self.label: "Passed"}
+
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCNumber(TC): # pragma: no cover
+
+ """
+ Number Comparaison Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+ self.supportedtypes = ("int", "long", "float", "complex", "oct", "hex")
+
+ # Compare 2 numbers by the type provided in the type arg
+ def compare(self, label, baseline, undertest, type):
+ """
+ Compares 2 numbers to see if they are the same. The user may specify
+ how to normalize mixed type comparisons via the type argument.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ self.type = type.strip()
+
+ # If we get a valid type, convert to that type and compare
+ if self.type in self.supportedtypes:
+ # Normalize for comparison
+ if self.type == "int":
+ self.baseline = int(self.baseline)
+ self.undertest = int(self.undertest)
+ elif self.type == "long":
+ self.baseline = long(self.baseline)
+ self.undertest = long(self.undertest)
+ elif self.type == "float":
+ self.baseline = float(self.baseline)
+ self.undertest = float(self.undertest)
+ else:
+ self.baseline = complex(self.baseline)
+ self.undertest = complex(self.undertest)
+
+ # compare
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed - numbers are the same"}
+ else:
+ self.result = {self.label: "Failed - " + str(
+ self.baseline) + " expected: Got " + str(self.undertest)}
+ TC.logger.log(self.result)
+ return self.result
+ else:
+ self.result = {
+ self.label: "Failed - " + self.type + " is not in list of supported types"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCBool(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, _bool):
+ """
+ If _bool is True, pass.
+ If _bool is False, fail.
+ """
+ if type(_bool) is not bool:
+ raise TypeError
+ if _bool:
+ result = {label: "Passed"}
+ else:
+ result = {label: "Failed"}
+ TC.logger.log(result)
+
+from tree import Node
+
+
+class TCNode(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, baseline, undertest):
+ """
+ If baseline is None, simply check that undertest is a Node.
+ If baseline is a Node, check that it is equal to undertest.
+ """
+ if baseline is not None and not isinstance(baseline, Node):
+ raise TypeError
+
+ if not isinstance(undertest, Node):
+ result = {label: "Failed - %s is not a Node" % undertest}
+ elif baseline is None:
+ result = {label: "Passed - %s is a Node" % undertest}
+ elif isinstance(baseline, Node):
+ if baseline == undertest:
+ result = {label: "Passed - %s == %s" % (baseline, undertest)}
+ else:
+ result = {label: "Failed - %s != %s" % (baseline, undertest)}
+ TC.logger.log(result)
diff --git a/build/lib/dogtail/tree.py b/build/lib/dogtail/tree.py
new file mode 100644
index 00000000000..e6050bbb98b
--- /dev/null
+++ b/build/lib/dogtail/tree.py
@@ -0,0 +1,1318 @@
+"""Makes some sense of the AT-SPI API
+
+The tree API handles various things for you:
+ - fixes most timing issues
+ - can automatically generate (hopefully) highly-readable logs of what the
+script is doing
+ - traps various UI malfunctions, raising exceptions for them (again,
+hopefully improving the logs)
+
+The most important class is Node. Each Node is an element of the desktop UI.
+There is a tree of nodes, starting at 'root', with applications as its
+children, with the top-level windows and dialogs as their children. The various
+widgets that make up the UI appear as descendents in this tree. All of these
+elements (root, the applications, the windows, and the widgets) are represented
+as instances of Node in a tree (provided that the program of interest is
+correctly exporting its user-interface to the accessibility system). The Node
+class is a mixin for Accessible and the various Accessible interfaces.
+
+The Action class represents an action that the accessibility layer exports as
+performable on a specific node, such as clicking on it. It's a wrapper around
+Accessibility.Action.
+
+We often want to look for a node, based on some criteria, and this is provided
+by the Predicate class.
+
+Dogtail implements a high-level searching system, for finding a node (or
+nodes) satisfying whatever criteria you are interested in. It does this with
+a 'backoff and retry' algorithm. This fixes most timing problems e.g. when a
+dialog is in the process of opening but hasn't yet done so.
+
+If a search fails, it waits 'config.searchBackoffDuration' seconds, and then
+tries again, repeatedly. After several failed attempts (determined by
+config.searchWarningThreshold) it will start sending warnings about the search
+to the debug log. If it still can't succeed after 'config.searchCutoffCount'
+attempts, it raises an exception containing details of the search. You can see
+all of this process in the debug log by setting 'config.debugSearching' to True
+
+We also automatically add a short delay after each action
+('config.defaultDelay' gives the time in seconds). We'd hoped that the search
+backoff and retry code would eliminate the need for this, but unfortunately we
+still run into timing issues. For example, Evolution (and probably most
+other apps) set things up on new dialogs and wizard pages as they appear, and
+we can run into 'setting wars' where the app resets the widgetry to defaults
+after our script has already filled out the desired values, and so we lose our
+values. So we give the app time to set the widgetry up before the rest of the
+script runs.
+
+The classes trap various UI malfunctions and raise exceptions that better
+describe what went wrong. For example, they detects attempts to click on an
+insensitive UI element and raise a specific exception for this.
+
+Unfortunately, some applications do not set up the 'sensitive' state
+correctly on their buttons (e.g. Epiphany on form buttons in a web page). The
+current workaround for this is to set config.ensureSensitivity=False, which
+disables the sensitivity testing.
+
+Authors: Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+from config import config
+if config.checkForA11y:
+ from utils import checkForA11y
+ checkForA11y()
+
+import predicate
+from time import sleep
+from utils import doDelay
+from utils import Blinker
+from utils import Lock
+import rawinput
+import path
+from __builtin__ import xrange
+
+from logging import debugLogger as logger
+
+try:
+ import pyatspi
+ import Accessibility
+except ImportError: # pragma: no cover
+ raise ImportError("Error importing the AT-SPI bindings")
+
+# We optionally import the bindings for libWnck.
+try:
+ from gi.repository import Wnck
+ gotWnck = True # pragma: no cover
+except ImportError:
+ # Skip this warning, since the functionality is almost entirely nonworking anyway.
+ # print "Warning: Dogtail could not import the Python bindings for
+ # libwnck. Window-manager manipulation will not be available."
+ gotWnck = False
+
+from gi.repository import GLib
+
+haveWarnedAboutChildrenLimit = False
+
+
+class SearchError(Exception):
+ pass
+
+
+class NotSensitiveError(Exception):
+
+ """
+ The widget is not sensitive.
+ """
+ message = "Cannot %s %s. It is not sensitive."
+
+ def __init__(self, action):
+ self.action = action
+
+ def __str__(self):
+ return self.message % (self.action.name, self.action.node.getLogString())
+
+
+class ActionNotSupported(Exception):
+
+ """
+ The widget does not support the requested action.
+ """
+ message = "Cannot do '%s' action on %s"
+
+ def __init__(self, actionName, node):
+ self.actionName = actionName
+ self.node = node
+
+ def __str__(self):
+ return self.message % (self.actionName, self.node.getLogString())
+
+
+class Action(object):
+
+ """
+ Class representing an action that can be performed on a specific node
+ """
+ # Valid types of actions we know about. Feel free to add any you see.
+ types = ('click',
+ 'press',
+ 'release',
+ 'activate',
+ 'jump',
+ 'check',
+ 'dock',
+ 'undock',
+ 'open',
+ 'menu')
+
+ def __init__(self, node, action, index):
+ self.node = node
+ self.__action = action
+ self.__index = index
+
+ @property
+ def name(self):
+ return self.__action.getName(self.__index)
+
+ @property
+ def description(self):
+ return self.__action.getDescription(self.__index)
+
+ @property
+ def keyBinding(self):
+ return self.__action.getKeyBinding(self.__index)
+
+ def __str__(self):
+ return "[action | %s | %s ]" % \
+ (self.name, self.keyBinding)
+
+ def do(self):
+ """
+ Performs the given tree.Action, with appropriate delays and logging.
+ """
+ logger.log("%s on %s" % (self.name, self.node.getLogString()))
+ if not self.node.sensitive:
+ if config.ensureSensitivity:
+ raise NotSensitiveError(self)
+ else:
+ nSE = NotSensitiveError(self)
+ logger.log("Warning: " + str(nSE))
+ if config.blinkOnActions:
+ self.node.blink()
+ result = self.__action.doAction(self.__index)
+ doDelay(config.actionDelay)
+ return result
+
+
+class Node(object):
+
+ """
+ A node in the tree of UI elements. This class is mixed in with
+ Accessibility.Accessible to both make it easier to use and to add
+ additional functionality. It also has a debugName which is set up
+ automatically when doing searches.
+ """
+
+ def __setupUserData(self):
+ try:
+ len(self.user_data)
+ except (AttributeError, TypeError):
+ self.user_data = {}
+
+ def debugName():
+ doc = "debug name assigned during search operations"
+
+ def fget(self):
+ self.__setupUserData()
+ return self.user_data.get('debugName', None)
+
+ def fset(self, debugName):
+ self.__setupUserData()
+ self.user_data['debugName'] = debugName
+
+ return property(**locals())
+ debugName = debugName()
+ #
+ # Accessible
+ #
+
+ @property
+ def dead(self):
+ """Is the node dead (defunct) ?"""
+ try:
+ if self.roleName == 'invalid':
+ return True
+ self.role
+ self.name
+ if len(self) > 0:
+ self[0]
+ except:
+ return True
+ return False
+
+ @property
+ def children(self):
+ """a list of this Accessible's children"""
+ if self.parent and self.parent.roleName == 'hyper link':
+ print(self.parent.role)
+ return []
+ children = []
+ childCount = self.childCount
+ if childCount > config.childrenLimit:
+ global haveWarnedAboutChildrenLimit
+ if not haveWarnedAboutChildrenLimit:
+ logger.log("Only returning %s children. You may change "
+ "config.childrenLimit if you wish. This message will only"
+ " be printed once." % str(config.childrenLimit))
+ haveWarnedAboutChildrenLimit = True
+ childCount = config.childrenLimit
+ for i in range(childCount):
+ # Workaround for GNOME bug #465103
+ # also solution for GNOME bug #321273
+ try:
+ child = self[i]
+ except LookupError:
+ child = None
+ if child:
+ children.append(child)
+
+ invalidChildren = childCount - len(children)
+ if invalidChildren and config.debugSearching:
+ logger.log("Skipped %s invalid children of %s" %
+ (invalidChildren, str(self)))
+ try:
+ ht = self.queryHypertext()
+ for li in range(ht.getNLinks()):
+ link = ht.getLink(li)
+ for ai in range(link.nAnchors):
+ child = link.getObject(ai)
+ child.__setupUserData()
+ child.user_data['linkAnchor'] = \
+ LinkAnchor(node=child,
+ hypertext=ht,
+ linkIndex=li,
+ anchorIndex=ai)
+ children.append(child)
+ except (NotImplementedError, AttributeError):
+ pass
+
+ return children
+
+ roleName = property(Accessibility.Accessible.getRoleName)
+
+ role = property(Accessibility.Accessible.getRole)
+
+ indexInParent = property(Accessibility.Accessible.getIndexInParent)
+
+ #
+ # Action
+ #
+
+ # Needed to be renamed from doAction due to conflicts
+ # with 'Accessibility.Accessible.doAction' in gtk3 branch
+ def doActionNamed(self, name):
+ """
+ Perform the action with the specified name. For a list of actions
+ supported by this instance, check the 'actions' property.
+ """
+ actions = self.actions
+ if name in actions:
+ return actions[name].do()
+ raise ActionNotSupported(name, self)
+
+ @property
+ def actions(self):
+ """
+ A dictionary of supported action names as keys, with Action objects as
+ values. Common action names include:
+
+ 'click' 'press' 'release' 'activate' 'jump' 'check' 'dock' 'undock'
+ 'open' 'menu'
+ """
+ actions = {}
+ try:
+ action = self.queryAction()
+ for i in range(action.nActions):
+ a = Action(self, action, i)
+ actions[action.getName(i)] = a
+ finally:
+ return actions
+
+ def combovalue():
+ doc = "The value (as a string) currently selected in the combo box."
+
+ def fget(self):
+ return self.name
+
+ def fset(self, value):
+ logger.log("Setting combobox %s to '%s'" % (self.getLogString(),
+ value))
+ self.childNamed(childName=value).doActionNamed('click')
+ doDelay()
+
+ return property(**locals())
+ combovalue = combovalue()
+ #
+ # Hypertext and Hyperlink
+ #
+
+ @property
+ def URI(self):
+ try:
+ return self.user_data['linkAnchor'].URI
+ except (KeyError, AttributeError):
+ raise NotImplementedError
+
+ #
+ # Text and EditableText
+ #
+ def text():
+ doc = """For instances with an AccessibleText interface, the text as a
+ string. This is read-only, unless the instance also has an
+ AccessibleEditableText interface. In this case, you can write values
+ to the attribute. This will get logged in the debug log, and a delay
+ will be added.
+
+ If this instance corresponds to a password entry, use the passwordText
+ property instead."""
+
+ def fget(self):
+ try:
+ return self.queryText().getText(0, -1)
+ except NotImplementedError:
+ return None
+
+ def fset(self, text):
+ try:
+ if config.debugSearching:
+ msg = "Setting text of %s to %s"
+ # Let's not get too crazy if 'text' is really large...
+ # FIXME: Sometimes the next line screws up Unicode strings.
+ if len(text) > 140:
+ txt = text[:134] + " [...]"
+ else:
+ txt = text
+ logger.log(msg % (self.getLogString(), "'%s'" % txt))
+ self.queryEditableText().setTextContents(text)
+ except NotImplementedError:
+ raise AttributeError("can't set attribute")
+
+ return property(**locals())
+ text = text()
+
+ def caretOffset():
+
+ def fget(self):
+ """For instances with an AccessibleText interface, the caret
+ offset as an integer."""
+ return self.queryText().caretOffset
+
+ def fset(self, offset):
+ return self.queryText().setCaretOffset(offset)
+
+ return property(**locals())
+ caretOffset = caretOffset()
+
+ #
+ # Component
+ #
+
+ @property
+ def position(self):
+ """A tuple containing the position of the Accessible: (x, y)"""
+ return self.queryComponent().getPosition(pyatspi.DESKTOP_COORDS)
+
+ @property
+ def size(self):
+ """A tuple containing the size of the Accessible: (w, h)"""
+ return self.queryComponent().getSize()
+
+ @property
+ def extents(self):
+ """A tuple containing the location and size of the Accessible:
+ (x, y, w, h)"""
+ try:
+ ex = self.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ return (ex.x, ex.y, ex.width, ex.height)
+ except NotImplementedError:
+ return None
+
+ def contains(self, x, y):
+ try:
+ return self.queryComponent().contains(x, y, pyatspi.DESKTOP_COORDS)
+ except NotImplementedError:
+ return False
+
+ def getChildAtPoint(self, x, y):
+ node = self
+ while True:
+ try:
+ child = node.queryComponent().getAccessibleAtPoint(x, y,
+ pyatspi.DESKTOP_COORDS)
+ if child and child.contains(x, y):
+ node = child
+ else:
+ break
+ except NotImplementedError:
+ break
+ if node and node.contains(x, y):
+ return node
+ else:
+ return None
+
+ def grabFocus(self):
+ "Attempts to set the keyboard focus to this Accessible."
+ return self.queryComponent().grabFocus()
+
+ # def blink(self, count=2):
+ #"""
+ # Blink, baby!
+ #"""
+ # if not self.extents: return False
+ # else:
+ #(x, y, w, h) = self.extents
+ #from utils import Blinker
+ #blinkData = Blinker(x, y, w, h, count)
+ # return True
+
+ def click(self, button=1):
+ """
+ Generates a raw mouse click event, using the specified button.
+ - 1 is left,
+ - 2 is middle,
+ - 3 is right.
+ """
+ logger.log("Clicking on %s" % self.getLogString())
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.click(clickX, clickY, button)
+
+ def doubleClick(self, button=1):
+ """
+ Generates a raw mouse double-click event, using the specified button.
+ """
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.doubleClick(clickX, clickY, button)
+
+ def point(self, mouseDelay=None):
+ """
+ Move mouse cursor to the center of the widget.
+ """
+ pointX = self.position[0] + self.size[0] / 2
+ pointY = self.position[1] + self.size[1] / 2
+ logger.log("Pointing on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(pointX), str(pointY)))
+ rawinput.registry.generateMouseEvent(pointX, pointY, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+ #
+ # RelationSet
+ #
+ @property
+ def labeler(self):
+ """'labeller' (read-only list of Node instances):
+ The node(s) that is/are a label for this node. Generated from
+ 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABELLED_BY:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labeller = labeler
+
+ @property
+ def labelee(self):
+ """'labellee' (read-only list of Node instances):
+ The node(s) that this node is a label for. Generated from 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labellee = labelee
+
+ #
+ # StateSet
+ #
+ @property
+ def sensitive(self):
+ """Is the Accessible sensitive (i.e. not greyed out)?"""
+ return self.getState().contains(pyatspi.STATE_SENSITIVE)
+
+ @property
+ def showing(self):
+ return self.getState().contains(pyatspi.STATE_SHOWING)
+
+ @property
+ def focusable(self):
+ """Is the Accessible capable of having keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSABLE)
+
+ @property
+ def focused(self):
+ """Does the Accessible have keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSED)
+
+ @property
+ def checked(self):
+ """Is the Accessible a checked checkbox?"""
+ return self.getState().contains(pyatspi.STATE_CHECKED)
+
+ @property
+ def isChecked(self):
+ """Is the Accessible a checked checkbox? Compatibility property, same as Node.checked."""
+ return self.checked
+
+ #
+ # Selection
+ #
+
+ def selectAll(self):
+ """Selects all children."""
+ result = self.querySelection().selectAll()
+ doDelay()
+ return result
+
+ def deselectAll(self):
+ """Deselects all selected children."""
+ result = self.querySelection().clearSelection()
+ doDelay()
+ return result
+
+ def select(self):
+ """Selects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().selectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ def deselect(self):
+ """Deselects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().deselectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ @property
+ def isSelected(self):
+ """Is the Accessible selected? Compatibility property, same as Node.selected."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ return parent.querySelection().isChildSelected(self.indexInParent)
+
+ @property
+ def selected(self):
+ """Is the Accessible selected?"""
+ return self.isSelected
+
+ @property
+ def selectedChildren(self):
+ """Returns a list of children that are selected."""
+ # TODO: hideChildren for Hyperlinks?
+ selection = self.querySelection()
+ selectedChildren = []
+ for i in xrange(selection.nSelectedChildren):
+ selectedChildren.append(selection.getSelectedChild(i))
+
+ #
+ # Value
+ #
+
+ def value():
+ doc = "The value contained by the AccessibleValue interface."
+
+ def fget(self):
+ try:
+ return self.queryValue().currentValue
+ except NotImplementedError:
+ pass
+
+ def fset(self, value):
+ self.queryValue().currentValue = value
+
+ return property(**locals())
+ value = value()
+
+ @property
+ def minValue(self):
+ """The minimum value of self.value"""
+ try:
+ return self.queryValue().minimumValue
+ except NotImplementedError:
+ pass
+
+ @property
+ def minValueIncrement(self):
+ """The minimum value increment of self.value"""
+ try:
+ return self.queryValue().minimumIncrement
+ except NotImplementedError:
+ pass
+
+ @property
+ def maxValue(self):
+ """The maximum value of self.value"""
+ try:
+ return self.queryValue().maximumValue
+ except NotImplementedError:
+ pass
+
+ def typeText(self, string):
+ """
+ Type the given text into the node, with appropriate delays and
+ logging.
+ """
+ logger.log("Typing text into %s: '%s'" % (self.getLogString(), string))
+
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ rawinput.typeText(string)
+ else:
+ logger.log("Node is not focusable; falling back to inserting text")
+ et = self.queryEditableText()
+ et.insertText(self.caretOffset, string, len(string))
+ self.caretOffset += len(string)
+ doDelay()
+
+ def keyCombo(self, comboString):
+ if config.debugSearching:
+ logger.log("Pressing keys '%s' into %s" %
+ (comboString, self.getLogString()))
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ else:
+ logger.log("Node is not focusable; trying key combo anyway")
+ rawinput.keyCombo(comboString)
+
+ def getLogString(self):
+ """
+ Get a string describing this node for the logs,
+ respecting the config.absoluteNodePaths boolean.
+ """
+ if config.absoluteNodePaths:
+ return self.getAbsoluteSearchPath()
+ else:
+ return str(self)
+
+ def satisfies(self, pred):
+ """
+ Does this node satisfy the given predicate?
+ """
+ # the logic is handled by the predicate:
+ assert isinstance(pred, predicate.Predicate)
+ return pred.satisfiedByNode(self)
+
+ def dump(self, type='plain', fileName=None):
+ import dump
+ dumper = getattr(dump, type)
+ dumper(self, fileName)
+
+ def getAbsoluteSearchPath(self):
+ """
+ FIXME: this needs rewriting...
+ Generate a SearchPath instance giving the 'best'
+ way to find the Accessible wrapped by this node again, starting
+ at the root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem,
+ except that some of searches may be recursive, rather than just
+ searching direct children.
+
+ Used by the recording framework for identifying nodes in a
+ persistent way, independent of the style of script being
+ written.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+ if config.debugSearchPaths:
+ logger.log("getAbsoluteSearchPath(%s)" % self)
+
+ if self.roleName == 'application':
+ result = path.SearchPath()
+ result.append(predicate.IsAnApplicationNamed(self.name), False)
+ return result
+ else:
+ if self.parent:
+ (ancestor, pred, isRecursive) = self.getRelativeSearch()
+ if config.debugSearchPaths:
+ logger.log("got ancestor: %s" % ancestor)
+
+ ancestorPath = ancestor.getAbsoluteSearchPath()
+ ancestorPath.append(pred, isRecursive)
+ return ancestorPath
+ else:
+ # This should be the root node:
+ return path.SearchPath()
+
+ def getRelativeSearch(self):
+ """
+ Get a (ancestorNode, predicate, isRecursive) triple that identifies the
+ best way to find this Node uniquely.
+ FIXME: or None if no such search exists?
+ FIXME: may need to make this more robust
+ FIXME: should this be private?
+ """
+ if config.debugSearchPaths:
+ logger.log("getRelativeSearchPath(%s)" % self)
+
+ assert self
+ assert self.parent
+
+ isRecursive = False
+ ancestor = self.parent
+
+ # iterate up ancestors until you reach an identifiable one,
+ # setting the search to be isRecursive if need be:
+ while not self.__nodeIsIdentifiable(ancestor):
+ ancestor = ancestor.parent
+ isRecursive = True
+
+ # Pick the most appropriate predicate for finding this node:
+ if self.labellee:
+ if self.labellee.name:
+ return (ancestor, predicate.IsLabelledAs(self.labellee.name), isRecursive)
+
+ if self.roleName == 'menu':
+ return (ancestor, predicate.IsAMenuNamed(self.name), isRecursive)
+ elif self.roleName == 'menu item' or self.roleName == 'check menu item':
+ return (ancestor, predicate.IsAMenuItemNamed(self.name), isRecursive)
+ elif self.roleName == 'text':
+ return (ancestor, predicate.IsATextEntryNamed(self.name), isRecursive)
+ elif self.roleName == 'push button':
+ return (ancestor, predicate.IsAButtonNamed(self.name), isRecursive)
+ elif self.roleName == 'frame':
+ return (ancestor, predicate.IsAWindowNamed(self.name), isRecursive)
+ elif self.roleName == 'dialog':
+ return (ancestor, predicate.IsADialogNamed(self.name), isRecursive)
+ else:
+ pred = predicate.GenericPredicate(
+ name=self.name, roleName=self.roleName)
+ return (ancestor, pred, isRecursive)
+
+ def __nodeIsIdentifiable(self, ancestor):
+ if ancestor.labellee:
+ return True
+ elif ancestor.name:
+ return True
+ elif not ancestor.parent:
+ return True
+ else:
+ return False
+
+ def _fastFindChild(self, pred, recursive=True):
+ """
+ Searches for an Accessible using methods from pyatspi.utils
+ """
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None:
+ if pred(child):
+ return child
+ else:
+ return pyatspi.utils.findDescendant(self, pred)
+
+ def findChild(self, pred, recursive=True, debugName=None,
+ retry=True, requireResult=True):
+ """
+ Search for a node satisyfing the predicate, returning a Node.
+
+ If retry is True (the default), it makes multiple attempts,
+ backing off and retrying on failure, and eventually raises a
+ descriptive exception if the search fails.
+
+ If retry is False, it gives up after one attempt.
+
+ If requireResult is True (the default), an exception is raised after all
+ attempts have failed. If it is false, the function simply returns None.
+ """
+ def describeSearch(parent, pred, recursive, debugName):
+ """
+ Internal helper function
+ """
+ if recursive:
+ noun = "descendent"
+ else:
+ noun = "child"
+ if debugName is None:
+ debugName = pred.describeSearchResult()
+ return "%s of %s: %s" % (noun, parent.getLogString(), debugName)
+
+ assert isinstance(pred, predicate.Predicate)
+ numAttempts = 0
+ while numAttempts < config.searchCutoffCount:
+ if numAttempts >= config.searchWarningThreshold or config.debugSearching:
+ logger.log("searching for %s (attempt %i)" %
+ (describeSearch(self, pred, recursive, debugName), numAttempts))
+
+ result = self._fastFindChild(pred.satisfiedByNode, recursive)
+ if result:
+ assert isinstance(result, Node)
+ if debugName:
+ result.debugName = debugName
+ else:
+ result.debugName = pred.describeSearchResult()
+ return result
+ else:
+ if not retry:
+ break
+ numAttempts += 1
+ if config.debugSearching or config.debugSleep:
+ logger.log("sleeping for %f" %
+ config.searchBackoffDuration)
+ sleep(config.searchBackoffDuration)
+ if requireResult:
+ raise SearchError(describeSearch(self, pred, recursive, debugName))
+
+ # The canonical "search for multiple" method:
+ def findChildren(self, pred, recursive=True, isLambda=False):
+ """
+ Find all children/descendents satisfying the predicate.
+ """
+ if isLambda is True:
+ nodes = self.findChildren(predicate.GenericPredicate(), recursive=recursive)
+ result = []
+ for node in nodes:
+ try:
+ if pred(node):
+ result.append(node)
+ except:
+ pass
+ return result
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ result = []
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None and pred(child):
+ result.append(child)
+ return result
+ else:
+ descendants = []
+ while True:
+ try:
+ descendants = pyatspi.utils.findAllDescendants(self, pred)
+ break
+ except (GLib.GError, TypeError):
+ continue
+ return descendants
+
+ # The canonical "search above this node" method:
+ def findAncestor(self, pred):
+ """
+ Search up the ancestry of this node, returning the first Node
+ satisfying the predicate, or None.
+ """
+ assert isinstance(pred, predicate.Predicate)
+ candidate = self.parent
+ while candidate is not None:
+ if candidate.satisfies(pred):
+ return candidate
+ else:
+ candidate = candidate.parent
+ # Not found:
+ return None
+
+ # Various wrapper/helper search methods:
+ def child(self, name='', roleName='', description='', label='', recursive=True, retry=True, debugName=None):
+ """
+ Finds a child satisying the given criteria.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description, label=label), recursive=recursive, retry=retry, debugName=debugName)
+
+ def isChild(self, name='', roleName='', description='', label='', recursive=True, retry=False, debugName=None):
+ """
+ Determines whether a child satisying the given criteria exists.
+
+ This is implemented using findChild, but will not automatically retry
+ if no such child is found. To make the function retry multiple times set retry to True.
+ Returns a boolean value depending on whether the child was eventually found. Similar to
+ 'child', yet it catches SearchError exception to provide for False results, will raise
+ any other exceptions. It also logs the search.
+ """
+ found = True
+ try:
+ self.findChild(
+ predicate.GenericPredicate(
+ name=name, roleName=roleName, description=description, label=label),
+ recursive=recursive, retry=retry, debugName=debugName)
+ except SearchError:
+ found = False
+ return found
+
+ def menu(self, menuName, recursive=True):
+ """
+ Search below this node for a menu with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuNamed(menuName=menuName), recursive)
+
+ def menuItem(self, menuItemName, recursive=True):
+ """
+ Search below this node for a menu item with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuItemNamed(menuItemName=menuItemName), recursive)
+
+ def textentry(self, textEntryName, recursive=True):
+ """
+ Search below this node for a text entry with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATextEntryNamed(textEntryName=textEntryName), recursive)
+
+ def button(self, buttonName, recursive=True):
+ """
+ Search below this node for a button with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAButtonNamed(buttonName=buttonName), recursive)
+
+ def childLabelled(self, labelText, recursive=True):
+ """
+ Search below this node for a child labelled with the given text.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsLabelledAs(labelText), recursive)
+
+ def childNamed(self, childName, recursive=True):
+ """
+ Search below this node for a child with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsNamed(childName), recursive)
+
+ def tab(self, tabName, recursive=True):
+ """
+ Search below this node for a tab with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATabNamed(tabName=tabName), recursive)
+
+ def getUserVisibleStrings(self):
+ """
+ Get all user-visible strings in this node and its descendents.
+
+ (Could be implemented as an attribute)
+ """
+ result = []
+ if self.name:
+ result.append(self.name)
+ if self.description:
+ result.append(self.description)
+ try:
+ children = self.children
+ except Exception:
+ return result
+ for child in children:
+ result.extend(child.getUserVisibleStrings())
+ return result
+
+ def blink(self):
+ """
+ Blink, baby!
+ """
+ if not self.extents:
+ return False
+ else:
+ (x, y, w, h) = self.extents
+ Blinker(x, y, w, h)
+ return True
+
+
+class LinkAnchor(object):
+
+ """
+ Class storing info about an anchor within an Accessibility.Hyperlink, which
+ is in turn stored within an Accessibility.Hypertext.
+ """
+
+ def __init__(self, node, hypertext, linkIndex, anchorIndex):
+ self.node = node
+ self.hypertext = hypertext
+ self.linkIndex = linkIndex
+ self.anchorIndex = anchorIndex
+
+ @property
+ def link(self):
+ return self.hypertext.getLink(self.linkIndex)
+
+ @property
+ def URI(self):
+ return self.link.getURI(self.anchorIndex)
+
+
+class Root (Node):
+
+ """
+ FIXME:
+ """
+
+ def applications(self):
+ """
+ Get all applications.
+ """
+ return root.findChildren(predicate.GenericPredicate(
+ roleName="application"), recursive=False)
+
+ def application(self, appName, retry=True):
+ """
+ Gets an application by name, returning an Application instance
+ or raising an exception.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return root.findChild(predicate.IsAnApplicationNamed(appName), recursive=False, retry=retry)
+
+
+class Application (Node):
+
+ def dialog(self, dialogName, recursive=False):
+ """
+ Search below this node for a dialog with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: should this method activate the dialog?
+ """
+ return self.findChild(predicate.IsADialogNamed(dialogName=dialogName), recursive)
+
+ def window(self, windowName, recursive=False):
+ """
+ Search below this node for a window with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: this bit isn't true:
+ The window will be automatically activated (raised and focused
+ by the window manager) if wnck bindings are available.
+ """
+ result = self.findChild(
+ predicate.IsAWindowNamed(windowName=windowName), recursive)
+ # FIXME: activate the WnckWindow ?
+ # if gotWnck:
+ # result.activate()
+ return result
+
+ def getWnckApplication(self): # pragma: no cover
+ """
+ Get the wnck.Application instance for this application, or None
+
+ Currently implemented via a hack: requires the app to have a
+ window, and looks up the application of that window
+
+ wnck.Application can give you the pid, the icon, etc
+
+ FIXME: untested
+ """
+ window = self.child(roleName='frame')
+ if window:
+ wnckWindow = window.getWnckWindow()
+ return wnckWindow.get_application()
+
+
+class Window (Node):
+
+ def getWnckWindow(self): # pragma: no cover
+ """
+ Get the wnck.Window instance for this window, or None
+ """
+ # FIXME: this probably needs rewriting:
+ screen = Wnck.screen_get_default()
+
+ # You have to force an update before any of the wnck methods
+ # do anything:
+ screen.force_update()
+
+ for wnckWindow in screen.get_windows():
+ # FIXME: a dubious hack: search by window title:
+ if wnckWindow.get_name() == self.name:
+ return wnckWindow
+
+ def activate(self): # pragma: no cover
+ """
+ Activates the wnck.Window associated with this Window.
+
+ FIXME: doesn't yet work
+ """
+ wnckWindow = self.getWnckWindow()
+ # Activate it with a timestamp of 0; this may confuse
+ # alt-tabbing through windows etc:
+ # FIXME: is there a better way of getting a timestamp?
+ # gdk_x11_get_server_time (), with a dummy window
+ wnckWindow.activate(0)
+
+
+class Wizard (Window):
+
+ """
+ Note that the buttons of a GnomeDruid were not accessible until
+ recent versions of libgnomeui. This is
+ http://bugzilla.gnome.org/show_bug.cgi?id=157936
+ and is fixed in gnome-2.10 and gnome-2.12 (in CVS libgnomeui);
+ there's a patch attached to that bug.
+
+ This bug is known to affect FC3; fixed in FC5
+ """
+
+ def __init__(self, node, debugName=None):
+ Node.__init__(self, node)
+ if debugName:
+ self.debugName = debugName
+ logger.log("%s is on '%s' page" % (self, self.getPageTitle()))
+
+ def currentPage(self):
+ """
+ Get the current page of this wizard
+
+ FIXME: this is currently a hack, supporting only GnomeDruid
+ """
+ pageHolder = self.child(roleName='panel')
+ for child in pageHolder.children:
+ # current child has SHOWING state set, we hope:
+ # print child
+ # print child.showing
+ if child.showing:
+ return child
+ raise "Unable to determine current page of %s" % self
+
+ def getPageTitle(self):
+ """
+ Get the string title of the current page of this wizard
+
+ FIXME: this is currently a total hack, supporting only GnomeDruid
+ """
+ currentPage = self.currentPage()
+ return currentPage.child(roleName='panel').child(roleName='panel').child(roleName='label', recursive=False).text
+
+ def clickForward(self):
+ """
+ Click on the 'Forward' button to advance to next page of wizard.
+
+ It will log the title of the new page that is reached.
+
+ FIXME: what if it's Next rather than Forward ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Forward")
+ fwd.click()
+
+ # Log the new wizard page; it's helpful when debugging scripts
+ logger.log("%s is now on '%s' page" % (self, self.getPageTitle()))
+ # FIXME disabled for now (can't get valid page titles)
+
+ def clickApply(self):
+ """
+ Click on the 'Apply' button to advance to next page of wizard.
+ FIXME: what if it's Finish rather than Apply ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Apply")
+ fwd.click()
+
+ # FIXME: debug logging?
+
+Accessibility.Accessible.__bases__ = (
+ Application, Root, Node,) + Accessibility.Accessible.__bases__
+
+try:
+ root = pyatspi.Registry.getDesktop(0)
+ root.debugName = 'root'
+except Exception: # pragma: no cover
+ # Warn if AT-SPI's desktop object doesn't show up.
+ logger.log(
+ "Error: AT-SPI's desktop is not visible. Do you have accessibility enabled?")
+
+# Check that there are applications running. Warn if none are.
+children = root.children
+if not children: # pragma: no cover
+ logger.log(
+ "Warning: AT-SPI's desktop is visible but it has no children. Are you running any AT-SPI-aware applications?")
+del children
+
+import os
+# sniff also imports from tree and we don't want to run this code from
+# sniff itself
+if not os.path.exists('/tmp/sniff_running.lock'):
+ if not os.path.exists('/tmp/sniff_refresh.lock'): # may have already been locked by dogtail.procedural
+ # tell sniff not to use auto-refresh while script using this module is
+ # running
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError: # pragma: no cover
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+# Convenient place to set some debug variables:
+#config.debugSearching = True
+#config.absoluteNodePaths = True
+#config.logDebugToFile = False
diff --git a/build/lib/dogtail/utils.py b/build/lib/dogtail/utils.py
new file mode 100644
index 00000000000..be3c78adec2
--- /dev/null
+++ b/build/lib/dogtail/utils.py
@@ -0,0 +1,395 @@
+# -*- coding: utf-8 -*-
+"""
+Various utilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+import os
+import sys
+import subprocess
+import cairo
+import predicate
+import errno
+import shlex
+
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from config import config
+from time import sleep
+from logging import debugLogger as logger
+from logging import TimeStamp
+from __builtin__ import file
+
+
+def screenshot(file='screenshot.png', timeStamp=True):
+ """
+ This function wraps the ImageMagick import command to take a screenshot.
+
+ The file argument may be specified as 'foo', 'foo.png', or using any other
+ extension that ImageMagick supports. PNG is the default.
+
+ By default, screenshot filenames are in the format of foo_YYYYMMDD-hhmmss.png .
+ The timeStamp argument may be set to False to name the file foo.png.
+ """
+ if not isinstance(timeStamp, bool):
+ raise TypeError("timeStampt must be True or False")
+ # config is supposed to create this for us. If it's not there, bail.
+ assert os.path.isdir(config.scratchDir)
+
+ baseName = ''.join(file.split('.')[0:-1])
+ fileExt = file.split('.')[-1].lower()
+ if not baseName:
+ baseName = file
+ fileExt = 'png'
+
+ if timeStamp:
+ ts = TimeStamp()
+ newFile = ts.fileStamp(baseName) + '.' + fileExt
+ path = config.scratchDir + newFile
+ else:
+ newFile = baseName + '.' + fileExt
+ path = config.scratchDir + newFile
+
+ from gi.repository import Gdk
+ from gi.repository import GLib
+ from gi.repository import GdkPixbuf
+ rootWindow = Gdk.get_default_root_window()
+ geometry = rootWindow.get_geometry()
+ pixbuf = GdkPixbuf.Pixbuf(colorspace=GdkPixbuf.Colorspace.RGB,
+ has_alpha=False,
+ bits_per_sample=8,
+ width=geometry[2],
+ height=geometry[3])
+
+ pixbuf = Gdk.pixbuf_get_from_window(rootWindow, 0, 0,
+ geometry[2], geometry[3])
+ # GdkPixbuf.Pixbuf.save() needs 'jpeg' and not 'jpg'
+ if fileExt == 'jpg':
+ fileExt = 'jpeg'
+ try:
+ pixbuf.savev(path, fileExt, [], [])
+ except GLib.GError:
+ raise ValueError("Failed to save screenshot in %s format" % fileExt)
+ assert os.path.exists(path)
+ logger.log("Screenshot taken: " + path)
+ return path
+
+
+def run(string, timeout=config.runTimeout, interval=config.runInterval, desktop=None, dumb=False, appName=''):
+ """
+ Runs an application. [For simple command execution such as 'rm *', use os.popen() or os.system()]
+ If dumb is omitted or is False, polls at interval seconds until the application is finished starting, or until timeout is reached.
+ If dumb is True, returns when timeout is reached.
+ """
+ if not desktop:
+ from tree import root as desktop
+ args = shlex.split(string)
+ os.environ['GTK_MODULES'] = 'gail:atk-bridge'
+ pid = subprocess.Popen(args, env=os.environ).pid
+
+ if not appName:
+ appName = args[0]
+
+ if dumb:
+ # We're starting a non-AT-SPI-aware application. Disable startup
+ # detection.
+ doDelay(timeout)
+ else:
+ # Startup detection code
+ # The timing here is not totally precise, but it's good enough for now.
+ time = 0
+ while time < timeout:
+ time = time + interval
+ try:
+ for child in desktop.children[::-1]:
+ if child.name == appName:
+ for grandchild in child.children:
+ if grandchild.roleName == 'frame':
+ from procedural import focus
+ focus.application.node = child
+ doDelay(interval)
+ return pid
+ except AttributeError: # pragma: no cover
+ pass
+ doDelay(interval)
+ return pid
+
+
+def doDelay(delay=None):
+ """
+ Utility function to insert a delay (with logging and a configurable
+ default delay)
+ """
+ if delay is None:
+ delay = config.defaultDelay
+ if config.debugSleep:
+ logger.log("sleeping for %f" % delay)
+ sleep(delay)
+
+
+class Highlight (Gtk.Window): # pragma: no cover
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ super(Highlight, self).__init__()
+ self.set_decorated(False)
+ self.set_has_resize_grip(False)
+ self.set_default_size(w, h)
+ self.screen = self.get_screen()
+ self.visual = self.screen.get_rgba_visual()
+ if self.visual is not None and self.screen.is_composited():
+ self.set_visual(self.visual)
+ self.set_app_paintable(True)
+ self.connect("draw", self.area_draw)
+ self.show_all()
+ self.move(x, y)
+
+ def area_draw(self, widget, cr): # pragma: no cover
+ cr.set_source_rgba(.0, .0, .0, 0.0)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
+ cr.set_source_rgb(0.9, 0.1, 0.1)
+ cr.set_line_width(6)
+ cr.rectangle(0, 0, self.get_size()[0], self.get_size()[1])
+ cr.stroke()
+
+
+class Blinker(object): # pragma: no cover
+ INTERVAL_MS = 1000
+ main_loop = GLib.MainLoop()
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ self.highlight_window = Highlight(x, y, w, h)
+ if self.highlight_window.screen.is_composited() is not False:
+ self.timeout_handler_id = GLib.timeout_add(
+ Blinker.INTERVAL_MS, self.destroyHighlight)
+ self.main_loop.run()
+ else:
+ self.highlight_window.destroy()
+
+ def destroyHighlight(self): # pragma: no cover
+ self.highlight_window.destroy()
+ self.main_loop.quit()
+ return False
+
+
+class Lock(object):
+
+ """
+ A mutex implementation that uses atomicity of the mkdir operation in UNIX-like
+ systems. This can be used by scripts to provide for mutual exlusion, either in single
+ scripts using threads etc. or i.e. to handle sitations of possible collisions among
+ multiple running scripts. You can choose to make randomized single-script wise locks
+ or a more general locks if you do not choose to randomize the lockdir name
+ """
+
+ def __init__(self, location='/tmp', lockname='dogtail_lockdir_', randomize=True):
+ """
+ You can change the default lockdir location or name. Setting randomize to
+ False will result in no random string being appened to the lockdir name.
+ """
+ self.lockdir = os.path.join(os.path.normpath(location), lockname)
+ if randomize:
+ self.lockdir = "%s%s" % (self.lockdir, self.__getPostfix())
+
+ def lock(self):
+ """
+ Creates a lockdir based on the settings on Lock() instance creation.
+ Raises OSError exception of the lock is already present. Should be
+ atomic on POSIX compliant systems.
+ """
+ locked_msg = 'Dogtail lock: Already locked with the same lock'
+ if not os.path.exists(self.lockdir):
+ try:
+ os.mkdir(self.lockdir)
+ return self.lockdir
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(self.lockdir):
+ raise OSError(locked_msg)
+ else:
+ raise OSError(locked_msg)
+
+ def unlock(self):
+ """
+ Removes a lock. Will raise OSError exception if the lock was not present.
+ Should be atomic on POSIX compliant systems.
+ """
+ import os # have to import here for situations when executed from __del__
+ if os.path.exists(self.lockdir):
+ try:
+ os.rmdir(self.lockdir)
+ except OSError as e:
+ if e.erron == errno.EEXIST:
+ raise OSError('Dogtail unlock: lockdir removed elsewhere!')
+ else:
+ raise OSError('Dogtail unlock: not locked')
+
+ def __del__(self):
+ """
+ Makes sure lock is removed when the process ends. Although not when killed indeed.
+ """
+ self.unlock()
+
+ def __getPostfix(self):
+ import random
+ import string
+ return ''.join(random.choice(string.letters + string.digits) for x in range(5))
+
+
+a11yDConfKey = 'org.gnome.desktop.interface'
+
+
+def isA11yEnabled():
+ """
+ Checks if accessibility is enabled via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(a11yDConfKey)
+ dconfEnabled = InterfaceSettings.get_boolean('toolkit-accessibility')
+ if os.environ.get('GTK_MODULES', '').find('gail:atk-bridge') == -1:
+ envEnabled = False
+ else:
+ envEnabled = True # pragma: no cover
+ return (dconfEnabled or envEnabled)
+
+
+def bailBecauseA11yIsDisabled():
+ if sys.argv[0].endswith("pydoc"):
+ return # pragma: no cover
+ try:
+ if file("/proc/%s/cmdline" % os.getpid()).read().find('epydoc') != -1:
+ return # pragma: no cover
+ except: # pragma: no cover
+ pass # pragma: no cover
+ logger.log("Dogtail requires that Assistive Technology support be enabled."
+ "\nYou can enable accessibility with sniff or by running:\n"
+ "'gsettings set org.gnome.desktop.interface toolkit-accessibility true'\nAborting...")
+ sys.exit(1)
+
+
+def enableA11y(enable=True):
+ """
+ Enables accessibility via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(schema=a11yDConfKey)
+ InterfaceSettings.set_boolean('toolkit-accessibility', enable)
+
+
+def checkForA11y():
+ """
+ Checks if accessibility is enabled, and halts execution if it is not.
+ """
+ if not isA11yEnabled(): # pragma: no cover
+ bailBecauseA11yIsDisabled()
+
+
+def checkForA11yInteractively(): # pragma: no cover
+ """
+ Checks if accessibility is enabled, and presents a dialog prompting the
+ user if it should be enabled if it is not already, then halts execution.
+ """
+ if isA11yEnabled():
+ return
+ from gi.repository import Gtk
+ dialog = Gtk.Dialog('Enable Assistive Technology Support?',
+ None,
+ Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ (Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE,
+ "_Enable", Gtk.ResponseType.ACCEPT))
+ question = """Dogtail requires that Assistive Technology Support be enabled for it to function. Would you like to enable Assistive Technology support now?
+
+Note that you will have to log out for the change to fully take effect.
+ """.strip()
+ dialog.set_default_response(Gtk.ResponseType.ACCEPT)
+ questionLabel = Gtk.Label(label=question)
+ questionLabel.set_line_wrap(True)
+ dialog.vbox.pack_start(questionLabel, True, True, 0)
+ dialog.show_all()
+ result = dialog.run()
+ if result == Gtk.ResponseType.ACCEPT:
+ logger.log("Enabling accessibility...")
+ enableA11y()
+ elif result == Gtk.ResponseType.CLOSE:
+ bailBecauseA11yIsDisabled()
+ dialog.destroy()
+
+
+class GnomeShell(object): # pragma: no cover
+
+ """
+ Utility class to help working with certain atributes of gnome-shell.
+ Currently that means handling the Application menu available for apps
+ on the top gnome-shell panel. Searching for the menu and its items is
+ somewhat tricky due to fuzzy a11y tree of gnome-shell, mainly since the
+ actual menu is not present as child to the menu-spawning button. Also,
+ the menus get constructed/destroyed on the fly with application focus
+ changes. Thus current application name as displayed plus a reference
+ known menu item (with 'Quit' as default) are required by these methods.
+ """
+
+ def __init__(self, classic_mode=False):
+ from tree import root
+ self.shell = root.application('gnome-shell')
+
+ def getApplicationMenuList(self, search_by_item='Quit'):
+ """
+ Returns list of all menu item nodes. Searches for the menu by a reference item.
+ Provide a different item name, if the 'Quit' is not present - but beware picking one
+ present elsewhere, like 'Lock' or 'Power Off' present under the user menu.
+ """
+ matches = self.shell.findChildren(
+ predicate.GenericPredicate(name=search_by_item, roleName='label'))
+ for match in matches:
+ ancestor = match.parent.parent.parent
+ if ancestor.roleName == 'panel':
+ return ancestor.findChildren(predicate.GenericPredicate(roleName='label'))
+ from tree import SearchError
+ raise SearchError("Could not find the Application menu based on '%s' item. Please provide an existing reference item"
+ % search_by_item)
+
+ def getApplicationMenuButton(self, app_name):
+ """
+ Returns the application menu 'button' node as present on the gnome-shell top panel.
+ """
+ try:
+ return self.shell[0][0][3].child(app_name, roleName='label')
+ except:
+ from tree import SearchError
+ raise SearchError(
+ "Application menu button of %s could not be found within gnome-shell!" % app_name)
+
+ def getApplicationMenuItem(self, item, search_by_item='Quit'):
+ """
+ Returns a particilar menu item node. Uses a different 'Quit' or custom item name for reference, but also
+ attempts to use the given item if the general reference fails.
+ """
+ try:
+ menu_items = self.getApplicationMenuList(search_by_item)
+ except:
+ menu_items = self.getApplicationMenuList(item)
+ for node in menu_items:
+ if node.name == item:
+ return node
+ raise Exception(
+ 'Could not find the item, did application focus change?')
+
+ def clickApplicationMenuItem(self, app_name, item, search_by_item='Quit'):
+ """
+ Executes the given menu item through opening the menu first followed
+ by a click at the particular item. The menu search reference 'Quit'
+ may be customized. Also attempts to use the given item for reference
+ if search fails with the default/custom one.
+ """
+ self.getApplicationMenuButton(app_name).click()
+ self.getApplicationMenuItem(item, search_by_item).click()
diff --git a/build/lib/dogtail/version.py b/build/lib/dogtail/version.py
new file mode 100644
index 00000000000..4d440806155
--- /dev/null
+++ b/build/lib/dogtail/version.py
@@ -0,0 +1,56 @@
+"""Handles versioning of software packages
+
+Author: Dave Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'Dave Malcolm <dmalcolm@redhat.com>'
+
+
+class Version(object):
+
+ """
+ Class representing a version of a software package.
+ Stored internally as a list of subversions, from major to minor.
+ Overloaded comparison operators ought to work sanely.
+ """
+
+ def __init__(self, versionList):
+ self.versionList = versionList
+
+ def fromString(versionString):
+ """
+ Parse a string of the form number.number.number
+ """
+ return Version(map(int, versionString.split(".")))
+ fromString = staticmethod(fromString)
+
+ def __str__(self):
+ return ".".join(map(str, self.versionList))
+
+ def __getNum(self):
+ tmpList = list(self.versionList)
+
+ while len(tmpList) < 5:
+ tmpList += [0]
+
+ num = 0
+ for i in range(len(tmpList)):
+ num *= 1000
+ num += tmpList[i]
+ return num
+
+ def __lt__(self, other):
+ return self.__getNum() < other.__getNum()
+
+ def __le__(self, other):
+ return self.__getNum() <= other.__getNum()
+
+ def __eq__(self, other):
+ return self.__getNum() == other.__getNum()
+
+ def __ne__(self, other):
+ return self.__getNum() != other.__getNum()
+
+ def __gt__(self, other):
+ return self.__getNum() > other.__getNum()
+
+ def __ge__(self, other):
+ return self.__getNum() >= other.__getNum()
diff --git a/build/lib/dogtail/wrapped.py b/build/lib/dogtail/wrapped.py
new file mode 100644
index 00000000000..30c0570fa61
--- /dev/null
+++ b/build/lib/dogtail/wrapped.py
@@ -0,0 +1,33 @@
+"""
+Superclasses for application wrappers
+
+Subclass these classes if you want to create application wrappers, e.g.:
+http://svn.gnome.org/viewvc/dogtail-tests/trunk/appwrappers/dogtail/appwrappers/gedit.py?view=markup
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+import Accessibility
+
+
+def makeWrapperClass(wrappedClass, name): # pragma: no cover
+ class klass(object):
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def __getattr__(self, name):
+ if name == 'obj':
+ return self.__dict__['obj']
+ return getattr(self.obj, name)
+
+ def __setattr__(self, name, value):
+ if name == 'obj':
+ self.__dict__['obj'] = value
+ else:
+ return setattr(self.obj, name, value)
+
+ klass.__name__ = name
+ return klass
+
+Application = makeWrapperClass(Accessibility.Application,
+ "WrappedApplication")
+Node = makeWrapperClass(Accessibility.Accessible, "WrappedNode")
diff --git a/build/scripts-2.7/dogtail-detect-session b/build/scripts-2.7/dogtail-detect-session
new file mode 100755
index 00000000000..79bb6170203
--- /dev/null
+++ b/build/scripts-2.7/dogtail-detect-session
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+"""
+dogtail-detect session
+
+This script checks for some main pieces of a running GNOME session,
+specifically gnome-panel and metacity.
+
+It checks to see that the gnome-panel node has at least some child nodes.
+For example, the main gnome-panel node by default has two direct descendents:
+the top panel, and the bottom panel.
+Metacity's existence is also checked. However, metacity currently never has
+any child nodes.
+
+If a proper session is running, the scripts exits with a status of 0.
+If no session is found, a non-zero exit code is returned.
+
+Author: Zack Cerza <zcerza@redhat.com>
+"""
+
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from dogtail.procedural import *
+import sys
+
+
+def GNOME():
+ """
+ "Is an accessibility-enabled GNOME session running?"
+ """
+ running = False
+ try:
+ assert focus.desktop
+ assert focus.desktop.children
+
+ focus.application('gnome-panel')
+ assert focus.application.children
+
+ focus.application('metacity')
+ print focus.application.node
+ assert focus.application.node
+ running = True
+ print "GNOME Session detected."
+ except AttributeError or AssertionError or FocusError:
+ print "ERROR: No session found."
+ return running
+
+
+def KDE():
+ """
+ "Is an accessibility-enabled KDE session running?"
+ """
+ running = False
+ return running
+
+
+def JustSomeApps():
+ """
+ "Is at least one accessibility-enabled application running?"
+ """
+ assert focus.desktop
+ assert focus.desktop.children
+
+if GNOME() or KDE() or JustSomeApps():
+ sys.exit()
+else:
+ sys.exit(1)
diff --git a/build/scripts-2.7/dogtail-logout b/build/scripts-2.7/dogtail-logout
new file mode 100755
index 00000000000..c76a4519e08
--- /dev/null
+++ b/build/scripts-2.7/dogtail-logout
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+# Logs out the full gnome session. Be sure to have your documents saved, as running
+# may cause loosing the changes, or it may halt the logout process.
+
+from dogtail.tree import *
+from dogtail.rawinput import pressKey
+from time import sleep
+import getpass
+
+# A gnome-shell object
+shell = root.application('gnome-shell')
+# Click onto a super menu label that we find under the g-s top panel object.
+# We need these indexes as g-s a11y support is a wee bit messy.
+shell[0][1][2].child(getpass.getuser(), roleName='label').click()
+# We can child this all the way down from the app as there's no other Log
+# Out... label
+shell.child('Log Out...', roleName='label').click()
+# This takes care of the 60 second dialog.
+# Sometimes a dialog warning about unsaved work in gedit etc. pops out, but that has the same
+# push button in which case this will take care of that dialog. If another dialog pops-out
+# in the affected application however, that might put the logout process on hold again. Unfortunatelly
+# we cannot do anything about that with dotail at that point as a11y registry got disabled already
+# by the logout process.
+shell[0][1].child(roleName='dialog', recursive=False).child(
+ 'Log Out', roleName='push button').click()
+
+# Give the session some time to end before we kill it.
+sleep(10)
diff --git a/build/scripts-2.7/dogtail-run-headless b/build/scripts-2.7/dogtail-run-headless
new file mode 100755
index 00000000000..edab53ddeed
--- /dev/null
+++ b/build/scripts-2.7/dogtail-run-headless
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+"""
+dogtail-run-headless
+
+This script runs a session within an X server, allowing dogtail scripts to be
+run on systems with no graphic cards, among other things.
+
+Scripts are run in the current directory. After they are finished, dogtail can
+optionally log out of the session, which will also termninate the X server.
+"""
+
+import optparse
+from dogtail import sessions
+import sys
+import os.path
+
+
+def findXServers(path="/usr/bin"):
+ l = [os.path.join(path, f) for f in os.listdir(path) if f[0] == 'X']
+ s = set(os.path.realpath(p) for p in l)
+ return list(s)
+
+
+def parse():
+ yesno = ('y', 'n')
+ sessions = ("GNOME", "KDE")
+ usage = "usage: %prog: [options] {script [args]}"
+ parser = optparse.OptionParser(usage=usage)
+
+ parser.add_option("-s", "--session", type="choice",
+ dest="session",
+ choices=sessions,
+ help="which session to use")
+ parser.add_option("-x", "--x-server", type="choice",
+ dest="xserver",
+ choices=findXServers(), help="which X server to use")
+ parser.add_option("-l", "--logout", type="choice",
+ dest="logout",
+ choices=yesno,
+ help="attempt to log out of the session gracefully after" +
+ "script completion")
+ parser.add_option("-t", "--terminate", type="choice",
+ dest="terminate",
+ choices=yesno,
+ help="after script completion, and after any attempt to log" +
+ "out, terminate the session")
+
+ parser.set_defaults(session=sessions[0], logout='y', terminate='y')
+ options, args = parser.parse_args()
+ if not args:
+ parser.print_usage()
+ sys.exit(1)
+ return options, args
+
+
+def main():
+ options, args = parse()
+ if 'XDG_RUNTIME_DIR' in os.environ:
+ del os.environ['XDG_RUNTIME_DIR']
+ if options.session == "GNOME":
+ session = sessions.Session(sessionBinary='/usr/bin/gnome-session',
+ scriptCmdList=args, scriptDelay=10)
+ if options.session == "KDE":
+ session = sessions.Session(sessionBinary='/usr/bin/startkde',
+ scriptCmdList=args, scriptDelay=25)
+ if options.xserver:
+ session.xserver.server = options.xserver
+ pid = session.start()
+ scriptExitCode = session.script.exitCode
+ if options.logout == 'y':
+ session.attemptLogout()
+ if options.terminate == 'y':
+ session.stop()
+ else:
+ session.wait()
+ sys.exit(scriptExitCode)
+
+if __name__ == "__main__":
+ main()
diff --git a/build/scripts-2.7/dogtail-run-headless-next b/build/scripts-2.7/dogtail-run-headless-next
new file mode 100755
index 00000000000..ae481cf1c44
--- /dev/null
+++ b/build/scripts-2.7/dogtail-run-headless-next
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+descr = """
+Unlike the original headless script this will make use of an Display Manager
+(DM - currently gdm) to handle starting the X server and user session. It's motivated
+by changes related to systemd - that disallows running a gnome session from an
+environment spawned by 'su'. The original headless will not work in these cases
+anymore on systemd systems
+
+Instead this script uses the AutoLogin feature of the DM, so that when it starts DM's
+service the session will login for particular user at once. It then uses the
+environment properties from the new session and runs the target script inthere.
+
+Will work with distros where 'service gdm/kdm start/stop' takes an effect, and quite
+likely only on systemd systems that use systemd-logind service.
+
+Even if you are still able to use dogtail-run-headless in your usecase, you might
+consider switching to this script - as making use of DM is likely to be more reliable
+and less buggy compared to headless itself taking care of everything.
+"""
+
+drop_overview = '''from dogtail.utils import absoluteMotion, keyPress
+absoluteMotion(100,100)
+keyPress('esc')'''
+
+import argparse
+import sys
+import os
+import glob
+import subprocess
+import time
+import ConfigParser
+import shutil
+import re
+from dogtail.sessions import Script
+
+preserve_envs = ['PYTHONPATH', 'TEST']
+
+
+def getSessionEnvironment(sessionBinary):
+
+ def isSessionProcess(fileName):
+ try:
+ if os.path.realpath(fileName + 'exe') != ('/usr/bin/plasma-desktop'
+ if sessionBinary.split('/')[-1] == 'startkde'
+ else sessionBinary):
+ return False
+ except OSError:
+ return False
+ pid = fileName.split('/')[2]
+ if pid == 'self' or pid == str(os.getpid()):
+ return False
+ return True
+
+ def getEnvDict(fileName):
+ try:
+ envString = open(fileName, 'r').read()
+ except IOError:
+ return {}
+ envItems = envString.split('\x00')
+ envDict = {}
+ for item in envItems:
+ if not '=' in item:
+ continue
+ k, v = item.split('=', 1)
+ envDict[k] = v
+ return envDict
+
+ def copyVars(envDict):
+ '''Copy a couple of old variables we want to preserve'''
+ for env in preserve_envs:
+ if os.environ.has_key(env):
+ envDict[env] = os.environ[env]
+ return envDict
+
+ envDict = False
+ for path in glob.glob('/proc/*/'):
+ if not isSessionProcess(path):
+ continue
+ envFile = path + 'environ'
+ envDict = getEnvDict(envFile)
+ if not envDict:
+ raise RuntimeError("Can't find our environment!")
+ return copyVars(envDict)
+
+
+def execCodeWithEnv(code, env=None):
+ with open("/tmp/execcode.dogtail", "w") as text_file:
+ text_file.write(code)
+ subprocess.Popen('python /tmp/execcode.dogtail'.split(),
+ env=(os.environ if env is None else env)).wait()
+
+
+class DisplayManagerSession(object):
+
+ gdm_config = '/etc/gdm/custom.conf'
+ kdm_config = '/etc/kde/kdm/kdmrc'
+ gdm_options = {'section': 'daemon', 'enable':
+ 'AutomaticLoginEnable', 'user': 'AutomaticLogin'}
+ kdm_options = {'section': 'X-:0-Core', 'enable':
+ 'AutoLoginEnable', 'user': 'AutoLoginUser'}
+ scriptDelay = 20
+ user = 'test'
+
+ def isProcessRunning(self, process):
+ '''Gives true if process can be greped out of full ps dump '''
+ s = subprocess.Popen(["ps", "axw"], stdout=subprocess.PIPE)
+ for x in s.stdout:
+ if re.search(process, x):
+ return True
+ return False
+
+ def waitForProcess(self, process, invert=False):
+ '''Waits until a process appears'''
+ while self.isProcessRunning(process) is invert:
+ time.sleep(1)
+
+ def __init__(self, dm='gdm', session='gnome', session_binary='gnome-shell', user=None):
+ self.session_binary = session_binary
+ self.session = session
+ self.accountfile = '/var/lib/AccountsService/users/%s' % self.user
+ if user is not None:
+ self.user = user
+ if dm == 'gdm':
+ self.tmp_file = '/tmp/%s' % os.path.basename(self.gdm_config)
+ self.options = self.gdm_options
+ self.config = self.gdm_config
+ elif dm == 'kdm':
+ self.tmp_file = '/tmp/%s' % os.path.basename(self.kdm_config)
+ self.options = self.kdm_options
+ self.config = self.kdm_config
+ self.dm = dm
+
+ def setup(self, restore=False):
+ shutil.copy(self.config, self.tmp_file)
+ config = ConfigParser.SafeConfigParser()
+ config.optionxform = str
+ config.read(self.tmp_file)
+ if not restore:
+ config.set(self.options['section'], self.options['enable'], 'true')
+ config.set(
+ self.options['section'], self.options['user'], self.user)
+ else:
+ config.remove_option(
+ self.options['section'], self.options['enable'])
+ config.remove_option(self.options['section'], self.options['user'])
+ output = open(self.tmp_file, 'w')
+ config.write(output)
+ output.flush()
+ subprocess.Popen('sudo mv -f %s %s' %
+ (self.tmp_file, self.config), shell=True).wait()
+ if not restore:
+ if 'kwin' in self.session_binary:
+ try:
+ os.makedirs(os.getenv('HOME') + '/.kde/env/')
+ except:
+ pass
+ subprocess.Popen(
+ 'echo "export QT_ACCESSIBILITY=1" > ~/.kde/env/qt-at-spi.sh', shell=True).wait()
+ if self.dm == 'gdm':
+ need_restart = False
+ tempfile = '/tmp/%s_headless' % self.user
+ subprocess.Popen('cp -f %s %s' % (self.accountfile, tempfile), shell=True).wait()
+ account_config = ConfigParser.SafeConfigParser()
+ account_config.optionxform = str
+ account_config.read(tempfile)
+ try:
+ saved_session = account_config.get('User', 'XSession')
+ if self.session is None:
+ if 'kde' in saved_session and 'gnome-shell' in self.session_binary:
+ self.session_binary = '/usr/bin/kwin'
+ elif 'gnome' in saved_session and 'kwin' in self.session_binary:
+ self.session_binary = '/usr/bin/gnome-shell'
+ elif saved_session != self.session:
+ account_config.set('User', 'XSession', self.session)
+ need_restart = True
+ except ConfigParser.NoSectionError:
+ account_config.add_section('User')
+ account_config.set('User', 'XSession', self.session if self.session else '')
+ account_config.set('User', 'SystemAccount', 'false')
+ need_restart = True
+ if need_restart:
+ output = open(tempfile, 'w')
+ account_config.write(output)
+ output.flush()
+ subprocess.Popen('sudo mv -f %s %s' % (tempfile, self.accountfile), shell=True).wait()
+ time.sleep(1)
+ os.system('sudo service accounts-daemon restart')
+ time.sleep(6) # prevent a possible race condition
+ os.system('sudo service systemd-logind restart')
+ time.sleep(6) # these are fast, but we still need to make sure no races happen
+ else:
+ subprocess.Popen('sudo rm -f %s' % tempfile, shell=True).wait()
+ elif self.dm == 'kdm':
+ if self.session is not None:
+ subprocess.Popen(
+ 'echo "[Desktop]\nSession=%s" > /home/%s/.dmrc' % (self.session, self.user), shell=True).wait()
+
+ def start(self, restart=False):
+ if restart:
+ subprocess.Popen(('sudo service %s stop' % (self.dm)).split())
+ time.sleep(0.5)
+ subprocess.Popen(('sudo service %s start' % (self.dm)).split())
+ self.waitForProcess(self.session_binary.split('/')[-1])
+ # some extra time for an environment (shell/kwin) to load all resources
+ # etc.
+ if self.dm == 'kdm':
+ time.sleep(10) # KDE keeps loading screen on untill all is loaded
+ else:
+ time.sleep(4) # GNOME shows stuff as it appears
+
+ def setA11y(self, enable):
+ subprocess.Popen('/usr/bin/gsettings set org.gnome.desktop.interface toolkit-accessibility %s'
+ % ('true' if enable else 'false'), shell=True, env=os.environ)
+# if enable: # mouse is at 0x0 at the start - which brings in the overview
+# execCodeWithEnv(drop_overview, env = os.environ)
+# time.sleep(2) # time for the overview to go away
+
+ def stop(self):
+ subprocess.Popen(('sudo service %s stop' % (self.dm)).split()).wait()
+ self.waitForProcess('/usr/bin/%s' % self.dm, invert=True)
+ time.sleep(3) # extra safe time
+ # did i.e. gnome-shell get stuck running?
+ if self.isProcessRunning(self.session_binary.split('/')[-1]):
+ print(
+ 'dogtail-run-headless-next: WARNING: %s still running, proceeding with kill -9' %
+ self.session_binary.split('/')[-1])
+ subprocess.Popen(
+ ('sudo pkill --signal 9 %s' % (self.session_binary.split('/')[-1])).split()).wait()
+ time.sleep(1)
+
+
+def parse():
+ parser = argparse.ArgumentParser(
+ prog='$ dogtail-run-headless-next', description=descr, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('script', help="""Command to execute the script""")
+ parser.add_argument('--session', required=False,
+ help="""What session to use. Not specifying results in system default session on first login
+ or test user's last session with follow-up logins.
+ Otherwise you can set any xsession desktop file name here (i.e. 'gnome-classic', 'gnome', 'lxde' etc.).
+ 'kde' defaults to 'kde-plasma for legacy reasons.'""")
+ parser.add_argument('--session-binary', required=False,
+ help="""Specify an in-session ever-running binary (full path) to get the environment from. Only needed for non-gnome/kde sessions.""")
+ parser.add_argument('--dm', required=False,
+ help="""What display manager to use for spawning session. Supported are 'gdm' (default) and 'kdm'.""")
+ parser.add_argument('--restart', action='store_true',
+ help="""Restart previously running display manager session before script execution.""")
+ parser.add_argument('--dont-start', action='store_true',
+ help="""Use already running session (doesn't have to be under Display Manager)""")
+ parser.add_argument('--dont-kill', action='store_true',
+ help="""Do not kill session when script exits.""")
+ parser.add_argument('--disable-a11y', action='store_true',
+ help="""Disable accessibility technologies on script(not session) exit.""")
+ return parser.parse_args()
+
+
+def main():
+ args = parse()
+ scriptCmdList = args.script.split()
+
+ if args.session is None or 'gnome' in args.session:
+ if args.session_binary is None:
+ args.session_binary = '/usr/bin/gnome-shell'
+ elif 'kde' in args.session:
+ if args.session_binary is None:
+ args.session_binary = '/usr/bin/kwin'
+ if args.session == 'kde':
+ args.session = 'kde-plasma'
+ else:
+ if args.session_binary is None:
+ print('dogtail-run-headless-next: Need to specify a --session-binary ever-present in the session to get env from.')
+ sys.exit(-1)
+ if args.dm == 'gdm' or args.dm is None:
+ dm_name = 'gdm'
+ elif args.dm == 'kdm':
+ dm_name = 'kdm'
+ else:
+ print('dogtail-run-headless-next: I do not recognize the display manager!')
+ sys.exit(-1)
+
+ print('dogtail-run-headless-next: Using display manager: %s' % dm_name)
+
+ dm = DisplayManagerSession(dm_name, args.session, args.session_binary)
+
+ if args.dont_start is not True:
+ dm.setup()
+ dm.start(restart=args.restart)
+
+ print('dogtail-run-headless-next: Using %s to bind to the session' % dm.session_binary)
+
+ try:
+ os.environ = getSessionEnvironment(dm.session_binary)
+ except:
+ print(
+ 'dogtail-run-headless-next: Could not get the environment from %s process' %
+ dm.session_binary)
+ dm.stop()
+ dm.setup(restore=True)
+ sys.exit(1)
+
+ if dm_name == 'gdm':
+ dm.setA11y(True)
+
+ script = Script(scriptCmdList)
+ scriptPid = script.start()
+ print('dogtail-run-headless-next: Started the script with PID %d' % scriptPid)
+ exitCode = script.wait()
+ print('dogtail-run-headless-next: The script has finnished with return code %d' % exitCode)
+
+ if args.disable_a11y is True:
+ dm.setA11y(False)
+
+ if args.dont_kill is False:
+ dm.stop()
+ dm.setup(restore=True)
+
+ sys.exit(exitCode)
+
+if __name__ == "__main__":
+ main()
diff --git a/build/scripts-2.7/sniff b/build/scripts-2.7/sniff
new file mode 100755
index 00000000000..f55adfa9663
--- /dev/null
+++ b/build/scripts-2.7/sniff
@@ -0,0 +1,798 @@
+#!/usr/bin/python
+# -*- coding: UTF8 -*-
+"""
+http://en.wikipedia.org/wiki/Model-view-controller
+
+The SniffApp class sets up all of sniff's widgets.
+
+Data storage is handled by the SniffModel class.
+There is no SniffView class; we just use a GtkTreeView.
+Data display is handled by the SniffController class.
+"""
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+import sys
+from dogtail.config import config
+
+if config.checkForA11y:
+ from dogtail.utils import checkForA11yInteractively
+ checkForA11yInteractively()
+
+config.logDebugToFile = False
+config.childrenLimit = 100000
+
+import pyatspi
+import Accessibility
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import Gio
+from gi.repository import GdkPixbuf
+from gi.repository import GObject
+from gi.repository import GLib
+
+builder = Gtk.Builder()
+
+
+class SniffApp(object):
+ appName = 'Sniff'
+ appAuthors = ['Zack Cerza <zcerza@redhat.com>',
+ 'David Malcolm <dmalcolm@redhat.com']
+
+ def __init__(self):
+ self.builder = builder
+ import os
+ if os.path.exists('sniff.ui'):
+ self.builder.add_from_file('sniff.ui')
+ else:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/': # in case the path is /bin/sniff
+ path = '/usr'
+ self.builder.add_from_file(path +
+ '/share/dogtail/glade/sniff.ui')
+ self.app = self.builder.get_object(self.appName)
+ try:
+ self.app.set_icon_from_file('../icons/dogtail-head.svg')
+ except Exception:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/':
+ path = '/usr'
+ self.app.set_icon_from_file(os.path.join(path,
+ 'share/icons/hicolor/scalable/apps/dogtail-head.svg'))
+ self.setUpWidgets()
+ self.connectSignals()
+ self.app.show_all()
+ Gtk.main()
+
+ def setUpWidgets(self):
+ self.quit1 = self.builder.get_object('quit1')
+ self.expand_all1 = self.builder.get_object('expand_all1')
+ self.collapse_all1 = self.builder.get_object('collapse_all1')
+ self.about1 = self.builder.get_object('about1')
+ self.refreshMenuItem = self.builder.get_object('refresh1')
+ self.autoRefreshMenuItem = self.builder.get_object('autorefresh')
+ self.setRootMenuItem = self.builder.get_object('setRootMenuItem')
+ self.unsetRootMenuItem = self.builder.get_object('unsetRootMenuItem')
+ self.about = None
+
+ self.tree = SniffController()
+
+ def connectSignals(self):
+ self.app.connect('delete_event', self.quit, self)
+ self.quit1.connect('activate', self.quit, self)
+ self.expand_all1.connect('activate', self.tree.expandAll, True)
+ self.collapse_all1.connect('activate', self.tree.expandAll, False)
+ self.about1.connect('activate', self.showAbout, self)
+ self.refreshMenuItem.connect('activate', self.tree.refresh)
+ self.autoRefreshMenuItem.connect(
+ 'toggled', self.tree.toggleAutoRefresh)
+ self.setRootMenuItem.connect('activate', self.tree.changeRoot, True)
+ self.unsetRootMenuItem.connect('activate', self.tree.changeRoot, False)
+
+ self.setStartupAutoRefresh()
+
+ def setStartupAutoRefresh(self):
+ import os
+ if not os.path.exists('/tmp/sniff_refresh.lock'):
+ self.autoRefreshMenuItem.set_active(True)
+
+ def showAbout(self, *args):
+ if not self.about:
+ self.about = Gtk.AboutDialog()
+ self.about.set_name(self.appName)
+ self.about.set_authors(self.appAuthors)
+ self.about.set_comments('Explore your desktop with Dogtail')
+ self.about.set_website('http://people.redhat.com/zcerza/dogtail/')
+ self.about.connect("response", self.hideAbout)
+ self.about.show_all()
+
+ def hideAbout(self, window, response):
+ if response == Gtk.ResponseType.CANCEL:
+ window.hide()
+
+ def quit(self, *args):
+ Gtk.main_quit()
+
+
+class SniffController(object):
+ invalidBufferCallbackID = None
+
+ def __init__(self):
+ self.builder = builder
+ self.nameTextLabel = self.builder.get_object('nameTextLabel')
+ self.nameTextLabel.set_text('')
+ self.roleNameTextLabel = self.builder.get_object('roleNameTextLabel')
+ self.roleNameTextLabel.set_text('')
+ self.descTextLabel = self.builder.get_object('descTextLabel')
+ self.descTextLabel.set_text('')
+ self.actionsTextLabel = self.builder.get_object('actionsTextLabel')
+ self.actionsTextLabel.set_text('')
+ self.textTextView = self.builder.get_object('textTextView')
+ self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
+ self.textTextView.set_sensitive(False)
+ self.textTextView.get_buffer().set_text('')
+ self.labelerButton = self.builder.get_object('labelerButton')
+ self.labelerButton.set_sensitive(False)
+ self.labeleeButton = self.builder.get_object('labeleeButton')
+ self.labeleeButton.set_sensitive(False)
+ self.stateView = self.builder.get_object('stateTreeView')
+ self.highlight1 = self.builder.get_object('highlight1')
+ self.autorefresh = self.builder.get_object('autorefresh')
+ self.stateModel = StateModel()
+ self.setUpStateView()
+ self.treeView = self.builder.get_object('treeTreeView')
+ self.treeSelection = self.treeView.get_selection()
+ self.treeModel = SniffModel()
+ self.setUpTreeView()
+ self.connectSignals()
+ self.refresh()
+
+ def setUpStateView(self):
+ self.stateView.set_model(self.stateModel)
+ cellRenderer = Gtk.CellRendererText()
+ col = Gtk.TreeViewColumn('Present States', cellRenderer,
+ text=self.stateModel.stateColumn)
+ col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
+ self.stateView.insert_column(col, -1)
+
+ def setUpTreeView(self):
+ self.treeView.set_enable_tree_lines(True)
+
+ self.treeView.set_model(self.treeModel)
+
+ col = Gtk.TreeViewColumn()
+ cellRenderer = Gtk.CellRendererPixbuf()
+ col.pack_start(cellRenderer, expand=False)
+ col.add_attribute(cellRenderer, 'pixbuf', self.treeModel.pixbufColumn)
+
+ cellRenderer = Gtk.CellRendererText()
+ col.pack_end(cellRenderer, expand=False)
+ col.add_attribute(cellRenderer, 'text', self.treeModel.nameColumn)
+
+ col.set_title('Name')
+
+ self.treeView.insert_column(col, -1)
+
+ for column in self.treeView.get_columns():
+ column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
+ column.set_resizable(True)
+ self.treeView.show()
+ path = 0
+ self.treeView.expand_all()
+ #self.rowExpanded(self.treeView, self.treeModel.get_iter(path), path)
+
+ def changeRoot(self, menuItem, toSelected=True, *args):
+ if toSelected:
+ node = self.getSelectedNode()
+ if toSelected and node:
+ self.treeModel.changeRoot(node)
+ elif not toSelected:
+ self.treeModel.reset()
+ else:
+ return
+ self.refresh(refreshModel=False)
+
+ def refresh(self, menuItem=None, refreshModel=True, *args):
+ if refreshModel:
+ self.treeModel.refresh()
+ rootPath = self.treeModel.get_path(self.treeModel.get_iter_first())
+ self.treeView.expand_all()
+ self.treeView.expand_row(rootPath, False)
+
+ def toggleAutoRefresh(self, *args):
+ if self.autorefresh.get_active() is True:
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:children-changed')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:property-change:accessible-name')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:property-change:accessible-state')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:state-changed')
+ self.refresh()
+ else:
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:children-changed')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:property-change:accessible-name')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:property-change:accessible-state')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:state-changed')
+
+ def connectSignals(self):
+ self.labelerButton.connect('clicked', self.showRelationTarget,
+ 'labeler')
+ self.labeleeButton.connect('clicked', self.showRelationTarget,
+ 'labelee')
+ self.treeView.connect('button-press-event', self.buttonPress)
+ self.treeView.connect('key-press-event', self.keyPress)
+ self.treeView.connect('row-expanded', self.rowExpanded, self.treeModel)
+ self.treeView.connect('row-collapsed', self.rowCollapsed)
+ self.treeSelection.connect('changed', self.selectionChanged)
+ self.refresh()
+
+ def selectionChanged(self, treeSelection):
+ node = self.getSelectedNode()
+ if node:
+ self.setUpBottomPane(node)
+ if self.highlight1.get_active() is True:
+ node.blink()
+
+ def getSelectedNode(self):
+ (store, iter) = self.treeView.get_selection().get_selected()
+ if not iter:
+ node = None
+ else:
+ node = self.treeModel.getNode(iter)
+ return node
+
+ def expandAll(self, widget, *args):
+ if args[0] == True:
+ self.treeView.expand_all()
+ elif args[0] == False:
+ self.treeView.collapse_all()
+
+ def rowExpanded(self, treeview, iter, path, *userParams):
+ row = self.treeModel[path]
+ childRows = row.iterchildren()
+ while True:
+ try:
+ childRow = childRows.next()
+ self.treeModel.populateChildren(childRow.iter)
+ except StopIteration:
+ break
+
+ def rowCollapsed(self, treeview, iter, path, *userParams):
+ row = self.treeModel[path]
+ childRows = row.iterchildren()
+ try:
+ while True:
+ childRow = childRows.next()
+ grandChildRows = childRow.iterchildren()
+ try:
+ while True:
+ grandChildRow = grandChildRows.next()
+ self.treeModel.remove(grandChildRow.iter)
+ except StopIteration:
+ pass
+ except StopIteration:
+ pass
+
+ def menuItemActivate(self, menuItem, *userParams):
+ if len(userParams) < 2:
+ return
+ method = userParams[0]
+ arg = userParams[1]
+ method(arg)
+
+ def keyPress(self, widget, event, *userParams):
+ if event.keyval == Gdk.KEY_Return:
+ path = self.treeSelection.get_selected_rows()[1][0]
+ if self.treeView.row_expanded(path):
+ self.treeView.collapse_row(path)
+ else:
+ self.treeView.expand_row(path, False)
+ return False
+
+ def buttonPress(self, widget, event, *userParams):
+ try:
+ path, treeViewCol, relX, relY = \
+ self.treeView.get_path_at_pos(int(event.x),
+ int(event.y))
+ except TypeError:
+ return
+ node = self.treeModel.getNode(self.treeModel.get_iter(path))
+ if node == None:
+ return
+
+ if event.button == 3:
+ self.menu = Gtk.Menu()
+ menuItem = None
+ if node.actions:
+ for action in node.actions.keys():
+ menuItem = Gtk.MenuItem(action.capitalize())
+ menuItem.connect(
+ 'activate', self.menuItemActivate, node.doActionNamed, action)
+ menuItem.show()
+ self.menu.append(menuItem)
+ if not menuItem:
+ return
+ self.menu.show_all()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+
+ def showRelationTarget(self, button, relation, *args):
+ target = getattr(self.getSelectedNode(), relation)
+ if not target:
+ return
+ try:
+ target.blink()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def setUpBottomPane(self, node):
+ """Generic code for setting up the table under the TreeView"""
+ if node == None:
+ return
+ self.nameTextLabel.set_text(node.name)
+ self.roleNameTextLabel.set_text(node.roleName)
+ self.descTextLabel.set_text(node.description)
+ str = ''
+ if node.actions:
+ str = ' '.join(node.actions.keys())
+ self.actionsTextLabel.set_text(str)
+
+ # Have we connected this signal yet?
+ # If so, disconnect it before proceeding.
+ if self.textTextViewBufferCallbackID != self.invalidBufferCallbackID:
+ self.textTextView.get_buffer().disconnect(
+ self.textTextViewBufferCallbackID)
+ self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
+
+ if node.text is not None:
+ buffer = self.textTextView.get_buffer()
+ buffer.set_text(node.text)
+ try:
+ node.queryEditableText()
+ # Remember the handler ID of this connection.
+ self.textTextView.set_sensitive(True)
+ self.textTextViewBufferCallbackID = \
+ buffer.connect('changed', self.changeText, node)
+ except NotImplementedError:
+ self.textTextView.set_sensitive(False)
+ else:
+ self.textTextView.get_buffer().set_text('')
+ self.textTextView.set_sensitive(False)
+
+ if node.labeler and not node.labeler.dead:
+ self.labelerButton.set_sensitive(True)
+ self.labelerButton.show()
+ # elif node.labeler and node.labeler.dead:
+ # print "labeler is dead", node.labeler
+ else:
+ self.labelerButton.set_sensitive(False)
+ self.labelerButton.hide()
+ if node.labelee and not node.labelee.dead:
+ self.labeleeButton.set_sensitive(True)
+ self.labeleeButton.show()
+ # elif node.labelee and node.labelee.dead:
+ # print "labelee is dead", node.labelee
+ else:
+ self.labeleeButton.set_sensitive(False)
+ self.labeleeButton.hide()
+
+ self.stateModel.setNode(node)
+
+ def changeText(self, textBuffer, node):
+ if node == None:
+ return
+ node.text = textBuffer.get_text(textBuffer.get_start_iter(),
+ textBuffer.get_end_iter())
+
+
+class SniffModel(Gtk.TreeStore):
+ nodeColumn = 0
+ nameColumn = 1
+ pixbufColumn = 2
+ eventQueue = []
+ cache = {}
+
+ def __init__(self):
+ self.builder = builder
+ #self.autorefresh = self.builder.get_object('autorefresh')
+ Gtk.TreeStore.__init__(self, GObject.TYPE_PYOBJECT,
+ GObject.TYPE_STRING, GdkPixbuf.Pixbuf)
+ root = pyatspi.Registry.getDesktop(0)
+ self.rootNode = self.initialRootNode = root
+ self.appendAndPopulate(None, self.rootNode)
+
+ def __contains__(self, item):
+ from dogtail.tree import Node
+ if isinstance(item, Node):
+ if item in self.cache:
+ row = self.cache[item]
+ # If row is None, we need to call getPath() to be sure
+ if not row:
+ path = self.getPath(item)
+ return path is not None
+ elif row in self:
+ return True
+ return False
+ elif isinstance(item, Gtk.TreeIter):
+ return self.iter_is_valid(item)
+ elif isinstance(item, list) or isinstance(item, tuple):
+ try:
+ iter = self.get_iter(item)
+ except ValueError:
+ return False
+ return iter in self
+ elif isinstance(item, Gtk.TreeRowReference):
+ return item.valid() and item.get_path() in self
+ else:
+ raise TypeError
+
+ def changeRoot(self, node):
+ self.rootNode = node
+ self.refresh()
+
+ def reset(self):
+ self.rootNode = self.initialRootNode
+ self.refresh()
+
+ def refresh(self):
+ self.cache.clear()
+ self.clear()
+ self.appendAndPopulate(None, self.rootNode)
+
+ def append(self, parentIter, node):
+ if node:
+ self.cache[node] = None
+ pb = self.getPixbufForNode(node)
+ return Gtk.TreeStore.append(self, parentIter, (node, node.name, pb))
+
+ def remove(self, iter):
+ node = self.getNode(iter)
+ try:
+ del self.cache[node]
+ finally:
+ return Gtk.TreeStore.remove(self, iter)
+
+ def populateChildren(self, iter):
+ if not iter in self:
+ return False
+ result = True
+ node = self.getNode(iter)
+ try:
+ for child in node.children:
+ if child in self:
+ continue
+ result = result and self.append(iter, child)
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ print("Dogtail: warning: omiting possibly broken at-spi application record")
+ return result
+
+ def appendAndPopulate(self, iter, node):
+ childIter = self.append(iter, node)
+ return self.populateChildren(childIter)
+
+ def getNode(self, iter):
+ if not iter in self:
+ return None
+ return self.get_value(iter, self.nodeColumn)
+
+ def getPath(self, node):
+ if not node:
+ raise ValueError
+ try:
+ indexInParent = node.indexInParent
+ except LookupError:
+ return None
+ root = pyatspi.Registry.getDesktop(0)
+ row = self.cache.get(node, None)
+ path = []
+ needParent = True
+ if row:
+ if row in self:
+ path = row.get_path()
+ else:
+ del self.cache[node]
+ elif node == self.rootNode:
+ indexInParent = 0
+ needParent = False
+ elif node.role == pyatspi.ROLE_APPLICATION or node.roleName == \
+ 'application':
+ path = [0]
+ indexInParent = list(root.children).index(node)
+ needParent = False
+ elif not node.parent:
+ return None
+ elif (0 <= indexInParent <= (len(node.parent) - 1)) and \
+ node.parent[indexInParent] != node:
+ return None
+ siblings = node.parent.children
+ sibIndex = siblings.index(node)
+ try:
+ if siblings[sibIndex] != node:
+ return None
+ else:
+ indexInParent = sibIndex
+ except ValueError:
+ return None
+ if type(path) == list:
+ if needParent:
+ parentPath = self.getPath(node.parent)
+ if parentPath is None:
+ return None
+ else:
+ path = list(parentPath)
+ path.append(indexInParent)
+
+ path = tuple(path)
+ try:
+ nodeByPath = self.getNode(self.get_iter(path))
+ if node != nodeByPath:
+ # print "%s is not %s!" % (node, nodeByPath)
+ return None
+ except ValueError:
+ # print "I smell a bug in %s..." % node.getApplication()
+ return None
+
+ #self.cache[node] = Gtk.TreeRowReference(self, path)
+ return path
+
+ def processEvents(self):
+ if not len(self.eventQueue):
+ return
+ queueCopy = self.eventQueue[:]
+ for event in queueCopy:
+ self.processChangedNode(event)
+ self.eventQueue.remove(event)
+ return False
+
+ def nodeChanged(self, event):
+ # if self.autorefresh.get_active() is False: return
+ node = event.source
+ if not node or not node in self:
+ return
+ app = event.host_application
+ if app and app.name == 'sniff':
+ return
+ self.eventQueue.append(event)
+ GObject.idle_add(self.processEvents)
+
+ def processChangedNode(self, event):
+ node = event.source
+ if not node or not node in self:
+ return
+ path = self.getPath(node)
+ try:
+ iter = self.get_iter(path)
+ except (ValueError, TypeError):
+ return
+ if event.type.major == "property-change":
+ if event.type.minor == "accessible-name":
+ node = self.getNode(iter)
+ self.set_value(iter, self.nameColumn, node.name)
+ elif event.type.minor == "accessible-state":
+ print str(event)
+ elif event.type.major == "state-changed":
+ print str(event)
+ elif event.type.major == "children-changed":
+ if event.type.minor == 'add':
+ for child in node.children:
+ if not child in self:
+ if len(child) > 0:
+ self.appendAndPopulate(iter, child)
+ else:
+ # If it has no children now, give it a sec
+ # to come up with some.
+ GObject.timeout_add(1000,
+ self.__addNodeCB, iter, child)
+ elif event.type.minor == 'remove':
+ self.__removeNodeCB(iter, node, path)
+
+ def __addNodeCB(self, iter, parent):
+ self.appendAndPopulate(iter, parent)
+ return False
+
+ def __removeNodeCB(self, iter, parent, path):
+ childRow = self.iter_children(iter)
+ while childRow is not None:
+ node = self.getNode(childRow)
+ if node is None:
+ break
+ if node and self.getNode(childRow) not in parent:
+ self.remove(childRow)
+ else:
+ childRow = self.iter_next(childRow)
+
+ def __populateCB(self, iter):
+ self.populateChildren(iter)
+ return False
+
+ def getPixbufForNode(self, node):
+ theme = Gtk.IconTheme().get_default()
+ try:
+ if node.role == pyatspi.ROLE_APPLICATION:
+ # FIXME: Use the pixbuf from libwcnk (if available):
+ # wnckApp = Application(node).getWnckApplication()
+ # if wnckApp
+ try:
+ return theme.load_icon(node.name, 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except GObject.GError:
+ try:
+ return theme.load_icon(node.name.lower(), 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except GObject.GError:
+ return None
+ elif node.parent:
+ return iconForRole[node.role]
+ else:
+ return theme.load_icon("user-desktop", 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except Exception:
+ return theme.load_icon("dialog-error", 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+
+
+class StateModel(Gtk.ListStore):
+ stateColumn = 0
+ statesSupported = ['checked', 'focusable', 'focused', 'sensitive',
+ 'showing']
+
+ def __init__(self):
+ Gtk.ListStore.__init__(self, GObject.TYPE_STRING)
+
+ def setNode(self, node):
+ self.clear()
+ for stateName in self.statesSupported:
+ if getattr(node, stateName) is True:
+ self.append((stateName.capitalize(),))
+
+
+def loadIcon(iconName):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file('icons/' + iconName)
+ except GObject.GError:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/':
+ path = '/usr'
+ iconName = os.path.join(path, 'share/dogtail/icons/', iconName)
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(iconName)
+ return pixbuf
+
+button_xpm = loadIcon("button.xpm")
+checkbutton_xpm = loadIcon("checkbutton.xpm")
+checkmenuitem_xpm = loadIcon("checkmenuitem.xpm")
+colorselection_xpm = loadIcon("colorselection.xpm")
+combo_xpm = loadIcon("combo.xpm")
+dialog_xpm = loadIcon("dialog.xpm")
+image_xpm = loadIcon("image.xpm")
+label_xpm = loadIcon("label.xpm")
+menubar_xpm = loadIcon("menubar.xpm")
+menuitem_xpm = loadIcon("menuitem.xpm")
+notebook_xpm = loadIcon("notebook.xpm")
+scrolledwindow_xpm = loadIcon("scrolledwindow.xpm")
+spinbutton_xpm = loadIcon("spinbutton.xpm")
+statusbar_xpm = loadIcon("statusbar.xpm")
+table_xpm = loadIcon("table.xpm")
+text_xpm = loadIcon("text.xpm")
+toolbar_xpm = loadIcon("toolbar.xpm")
+tree_xpm = loadIcon("tree.xpm")
+treeitem_xpm = loadIcon("treeitem.xpm")
+unknown_xpm = loadIcon("unknown.xpm")
+viewport_xpm = loadIcon("viewport.xpm")
+vscrollbar_xpm = loadIcon("vscrollbar.xpm")
+vseparator_xpm = loadIcon("vseparator.xpm")
+window_xpm = loadIcon("window.xpm")
+
+iconForRole = {
+ pyatspi.ROLE_INVALID: None,
+ # pyatspi doesn't have the following... not even sure if it exists
+ # anywhere.
+ # atspi.SPI_ROLE_ACCEL_LABEL : label_xpm,
+ pyatspi.ROLE_ALERT: None,
+ pyatspi.ROLE_ANIMATION: None,
+ pyatspi.ROLE_ARROW: None,
+ pyatspi.ROLE_CALENDAR: None,
+ pyatspi.ROLE_CANVAS: None,
+ pyatspi.ROLE_CHECK_BOX: checkbutton_xpm,
+ pyatspi.ROLE_CHECK_MENU_ITEM: checkmenuitem_xpm,
+ pyatspi.ROLE_COLOR_CHOOSER: colorselection_xpm,
+ pyatspi.ROLE_COLUMN_HEADER: None,
+ pyatspi.ROLE_COMBO_BOX: combo_xpm,
+ pyatspi.ROLE_DATE_EDITOR: None,
+ pyatspi.ROLE_DESKTOP_ICON: None,
+ pyatspi.ROLE_DESKTOP_FRAME: None,
+ pyatspi.ROLE_DIAL: None,
+ pyatspi.ROLE_DIALOG: dialog_xpm,
+ pyatspi.ROLE_DIRECTORY_PANE: None,
+ pyatspi.ROLE_DRAWING_AREA: None,
+ pyatspi.ROLE_FILE_CHOOSER: None,
+ pyatspi.ROLE_FILLER: None,
+ pyatspi.ROLE_FONT_CHOOSER: None,
+ pyatspi.ROLE_FRAME: window_xpm,
+ pyatspi.ROLE_GLASS_PANE: None,
+ pyatspi.ROLE_HTML_CONTAINER: None,
+ pyatspi.ROLE_ICON: image_xpm,
+ pyatspi.ROLE_IMAGE: image_xpm,
+ pyatspi.ROLE_INTERNAL_FRAME: None,
+ pyatspi.ROLE_LABEL: label_xpm,
+ pyatspi.ROLE_LAYERED_PANE: viewport_xpm,
+ pyatspi.ROLE_LIST: None,
+ pyatspi.ROLE_LIST_ITEM: None,
+ pyatspi.ROLE_MENU: menuitem_xpm,
+ pyatspi.ROLE_MENU_BAR: menubar_xpm,
+ pyatspi.ROLE_MENU_ITEM: menuitem_xpm,
+ pyatspi.ROLE_OPTION_PANE: None,
+ pyatspi.ROLE_PAGE_TAB: notebook_xpm,
+ pyatspi.ROLE_PAGE_TAB_LIST: notebook_xpm,
+ pyatspi.ROLE_PANEL: viewport_xpm,
+ pyatspi.ROLE_PASSWORD_TEXT: None,
+ pyatspi.ROLE_POPUP_MENU: None,
+ pyatspi.ROLE_PROGRESS_BAR: None,
+ pyatspi.ROLE_PUSH_BUTTON: button_xpm,
+ pyatspi.ROLE_RADIO_BUTTON: None,
+ pyatspi.ROLE_RADIO_MENU_ITEM: None,
+ pyatspi.ROLE_ROOT_PANE: viewport_xpm,
+ pyatspi.ROLE_ROW_HEADER: None,
+ pyatspi.ROLE_SCROLL_BAR: vscrollbar_xpm,
+ pyatspi.ROLE_SCROLL_PANE: scrolledwindow_xpm,
+ pyatspi.ROLE_SEPARATOR: vseparator_xpm,
+ pyatspi.ROLE_SLIDER: None,
+ pyatspi.ROLE_SPIN_BUTTON: spinbutton_xpm,
+ pyatspi.ROLE_SPLIT_PANE: None,
+ pyatspi.ROLE_STATUS_BAR: statusbar_xpm,
+ pyatspi.ROLE_TABLE: table_xpm,
+ pyatspi.ROLE_TABLE_CELL: treeitem_xpm,
+ pyatspi.ROLE_TABLE_COLUMN_HEADER: None,
+ pyatspi.ROLE_TABLE_ROW_HEADER: None,
+ pyatspi.ROLE_TEAROFF_MENU_ITEM: None,
+ pyatspi.ROLE_TERMINAL: None,
+ pyatspi.ROLE_TEXT: text_xpm,
+ pyatspi.ROLE_TOGGLE_BUTTON: None,
+ pyatspi.ROLE_TOOL_BAR: toolbar_xpm,
+ pyatspi.ROLE_TOOL_TIP: None,
+ pyatspi.ROLE_TREE: tree_xpm,
+ pyatspi.ROLE_TREE_TABLE: tree_xpm,
+ pyatspi.ROLE_UNKNOWN: unknown_xpm,
+ pyatspi.ROLE_VIEWPORT: viewport_xpm,
+ pyatspi.ROLE_WINDOW: window_xpm,
+ pyatspi.ROLE_EXTENDED: None,
+ pyatspi.ROLE_HEADER: None,
+ pyatspi.ROLE_FOOTER: None,
+ pyatspi.ROLE_PARAGRAPH: None,
+ pyatspi.ROLE_RULER: None,
+ pyatspi.ROLE_APPLICATION: None,
+ pyatspi.ROLE_AUTOCOMPLETE: None,
+ pyatspi.ROLE_EDITBAR: None,
+ pyatspi.ROLE_EMBEDDED: None,
+ pyatspi.ROLE_LAST_DEFINED: None}
+
+
+def main():
+ from dogtail.utils import Lock
+ # We need this to prohibit sniff making(and removing on exit)
+ # sniff_refresh lock when importing Node
+ sniff_running = Lock(lockname='sniff_running.lock', randomize=False)
+ try:
+ sniff_running.lock()
+ except OSError:
+ pass
+ sniff = SniffApp()
+
+if __name__ == '__main__':
+ main()
diff --git a/dogtail.spec b/dogtail.spec
new file mode 100644
index 00000000000..9a4fc47694a
--- /dev/null
+++ b/dogtail.spec
@@ -0,0 +1,213 @@
+Summary: GUI test tool and automation framework
+Name: dogtail
+Version: 0.9.0
+Release: 1%{?dist}
+License: GPLv2
+URL: http://dogtail.fedorahosted.org/
+Source0: http://fedorahosted.org/released/dogtail/%{name}-%{version}.tar.gz
+
+BuildArch: noarch
+
+BuildRequires: python2-devel
+BuildRequires: desktop-file-utils
+
+Requires: pyatspi
+Requires: pygobject3
+Requires: pycairo
+Requires: rpm-python
+Requires: xorg-x11-xinit
+Requires: python-imaging
+
+%description
+GUI test tool and automation framework that uses assistive technologies to
+communicate with desktop applications.
+
+%prep
+%setup -q -n %{name}-%{version}
+
+%build
+python ./setup.py build
+
+%install
+python ./setup.py install -O2 --root=$RPM_BUILD_ROOT --record=%{name}.files
+rm -rf $RPM_BUILD_ROOT/%{_docdir}/dogtail
+rm -f $RPM_BUILD_ROOT/%{python_sitelib}/%{name}-%{version}-py%{python_version}.egg-info
+find examples -type f -exec chmod 0644 \{\} \;
+desktop-file-install $RPM_BUILD_ROOT/%{_datadir}/applications/sniff.desktop \
+ --dir=$RPM_BUILD_ROOT/%{_datadir}/applications \
+
+%post
+/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
+
+%postun
+if [ $1 -eq 0 ] ; then
+ /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null
+ /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
+fi
+
+%posttrans
+/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
+
+%files
+%{_bindir}/*
+%{python_sitelib}/dogtail/
+%{_datadir}/applications/*
+%{_datadir}/dogtail/
+%{_datadir}/icons/hicolor/*
+%doc COPYING
+%doc README
+%doc NEWS
+%doc examples/
+
+%changelog
+* Wed Apr 16 2014 Vitezslav Humpa <vhumpa@redhat.com> - 0.9.0-1
+- Update to upstream version 0.9.0
+
+* Tue Jul 9 2013 Vitezslav Humpa <vhumpa@redhat.com> - 0.8.2-1
+- Update to upstream version 0.8.2
+
+* Sat Feb 23 2013 Rahul Sundaram <sundaram@fedoraproject.org> - 0.8.1-5
+- drop unversioned obsolete
+
+* Sat Feb 23 2013 Rahul Sundaram <sundaram@fedoraproject.org> - 0.8.1-4
+- remove vendor tag from desktop file. https://fedorahosted.org/fpc/ticket/247
+- clean up spec to follow current guidelines
+
+* Wed Feb 13 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.8.1-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
+* Wed Jul 18 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.8.0-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild
+
+* Mon Jun 04 2012 Jaroslav Reznik <jreznik@redhat.com> - 0.8.0-2
+- respin
+
+* Thu May 31 2012 Jaroslav Reznik <jreznik@redhat.com> - 0.8.0-1
+- Update to 0.8.0 Final
+- New upstream release
+
+* Mon Apr 16 2012 Jaroslav Reznik <jreznik@redhat.com> - 0.8.0-0.5.beta5
+- Update to 0.8.0 beta 5
+
+* Mon Apr 02 2012 Jaroslav Reznik <jreznik@redhat.com> - 0.8.0-0.2.beta2
+- Update to 0.8.0 beta 2
+
+* Mon Mar 19 2012 Jaroslav Reznik <jreznik@redhat.com> - 0.8.0-0.1.beta1
+- Update to 0.8.0 beta 1
+
+* Fri Jan 13 2012 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.7.0-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild
+
+* Tue Feb 08 2011 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.7.0-3
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild
+
+* Wed Jul 21 2010 David Malcolm <dmalcolm@redhat.com> - 0.7.0-2
+- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild
+
+* Thu Oct 08 2009 Zack Cerza <zcerza@redhat.com> - 0.7.0-1
+- New upstream release.
+- Drop Requires on xorg-x11-server-Xvfb.
+- Update URL and Source0.
+- Ship NEWS file.
+
+* Fri Jul 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.6.90-4.401
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild
+
+* Tue Feb 24 2009 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 0.6.90-3.401
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild
+
+* Sat Nov 29 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm@gmail.com> - 0.6.90-2.401
+- Rebuild for Python 2.6
+
+* Tue Aug 12 2008 Zack Cerza <zcerza@redhat.com> - 0.6.90-1.401
+- New upstream snapshot.
+- Require python-imaging
+
+* Tue Aug 12 2008 Zack Cerza <zcerza@redhat.com> - 0.6.90-1.381.2
+- Really fix license tag.
+
+* Wed Jul 16 2008 Tom "spot" Callaway <tcallawa@redhat.com> - 0.6.90-1.381.1
+- fix license tag
+
+* Thu Jan 31 2008 Zack Cerza <zcerza@redhat.com> - 0.6.90-1.381
+- New upstream snapshot.
+- Obsolete pyspi; Require at-spi-python.
+- Require pygtk2-libglade.
+- Don't ship the .egg-info file.
+
+* Wed Jan 3 2007 Zack Cerza <zcerza@redhat.com> - 0.6.1-1
+- New upstream release.
+
+* Thu Dec 7 2006 Jeremy Katz <katzj@redhat.com> - 0.6.0-2
+- build for python 2.5
+- BR python-devel
+
+* Wed Sep 13 2006 Zack Cerza <zcerza@redhat.com> - 0.6.0-1
+- New upstream release.
+- Add Requires for xorg-x11-xinit.
+- Add Requires for gnome-python2-gconf.
+- Bump pyspi Requires.
+- Remove upstreamed patches.
+
+* Fri Aug 18 2006 Zack Cerza <zcerza@redhat.com> - 0.5.2-3
+- Add Requires for xorg-x11-xinit. Closes: #203189.
+
+* Fri Aug 11 2006 Zack Cerza <zcerza@redhat.com> - 0.5.2-2
+- Added headless-gconf.patch to use the python gconf bindings.
+- Added desktop-file-categories.patch to put sniff and dogtail-recorder under
+ the 'Programming' menu.
+
+* Tue Aug 01 2006 Zack Cerza <zcerza@redhat.com> - 0.5.2-1
+- New upstream release.
+- Update Requires from Xvfb to xorg-x11-server-Xvfb.
+- Bump pyspi Requires.
+- Remove ImageMagick Requires.
+- Escape post-macro in changelog-macro.
+
+* Mon Apr 17 2006 Zack Cerza <zcerza@redhat.com> - 0.5.1-3
+- Fix the URL field.
+
+* Tue Mar 21 2006 Zack Cerza <zcerza@redhat.com> - 0.5.1-2
+- Fix URL and Source0 fields.
+- Fix desktop-file-utils magic; use desktop-file-install.
+
+* Fri Feb 24 2006 Zack Cerza <zcerza@redhat.com> - 0.5.1-1
+- Remove BuildRequires on at-spi-devel. Added one on python.
+- Use macros instead of absolute paths.
+- Touch _datadir/icons/hicolor/ before running gtk-update-icon-cache.
+- Require and use desktop-file-utils.
+- postun = post.
+- Shorten BuildArchitectures to BuildArch. The former worked, but even vim's
+ hilighting hated it.
+- Put each *Requires on a separate line.
+- Remove __os_install_post definition.
+- Use Fedora Extras BuildRoot.
+- Instead of _libdir, which kills the build if it's /usr/lib64, use a
+ python macro to define python_sitelib and use that.
+- Remove the executable bit on the examples in install scriptlet.
+- Remove call to /bin/rm in post scriptlet.
+- Use dist in Release.
+
+* Fri Feb 17 2006 Zack Cerza <zcerza@redhat.com> - 0.5.0-2
+- It looks like xorg-x11-Xvfb changed names. Require 'Xvfb' instead.
+- Remove Requires on python-elementtree, since RHEL4 didn't have it. The
+ functionality it provides is probably never used anyway, and will most likely
+ be removed in the future.
+- Don't run gtk-update-icon-cache if it doesn't exist.
+
+* Fri Feb 3 2006 Zack Cerza <zcerza@redhat.com> - 0.5.0-1
+- New upstream release.
+- Added missing BuildRequires on at-spi-devel.
+- Added Requires on pyspi >= 0.5.3.
+- Added Requires on rpm-python, pygtk2, ImageMagick, xorg-x11-Xvfb,
+ python-elementtree.
+- Moved documentation (including examples) to the correct place.
+- Make sure /usr/share/doc/dogtail is removed.
+- Added 'gtk-update-icon-cache' to %%post.
+
+* Mon Oct 24 2005 Zack Cerza <zcerza@redhat.com> - 0.4.3-1
+- New upstream release.
+
+* Sat Oct 8 2005 Jeremy Katz <katzj@redhat.com> - 0.4.2-1
+- Initial build.
+
diff --git a/dogtail/__init__.py b/dogtail/__init__.py
new file mode 100755
index 00000000000..76a4a13fcb5
--- /dev/null
+++ b/dogtail/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: UTF-8 -*-
+"""
+GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications.
+
+Authors: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vita Humpa <vhumpa@redhat.com>
+"""
+
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+Ed Rousseau <rousseau@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>,
+Vita Humpa <vhumpa@redhat.com>"""
+__version__ = "0.9.0"
+__copyright__ = "Copyright © 2005-2014 Red Hat, Inc."
+__license__ = "GPL"
+__all__ = ("config", "predicate",
+ "procedural", "tc", "tree", "utils", "errors")
diff --git a/dogtail/__init__.pyc b/dogtail/__init__.pyc
new file mode 100644
index 00000000000..3c6e83c4253
--- /dev/null
+++ b/dogtail/__init__.pyc
Binary files differ
diff --git a/dogtail/config.py b/dogtail/config.py
new file mode 100755
index 00000000000..82e197cf523
--- /dev/null
+++ b/dogtail/config.py
@@ -0,0 +1,219 @@
+"""
+The configuration module.
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>"
+
+import os
+import sys
+import locale
+
+
+def _userTmpDir(baseName):
+ # i.e. /tmp/dogtail-foo
+ return '-'.join(('/'.join(('/tmp', baseName)), os.environ['USER']))
+
+
+class _Config(object):
+
+ """
+ Contains configuration parameters for the dogtail run.
+
+ scratchDir(str):
+ Directory where things like screenshots are stored.
+
+ dataDir(str):
+ Directory where related data files are located.
+
+ logDir(str):
+ Directory where dogtail.tc.TC*-generated logs are stored.
+
+ scriptName(str) [Read-Only]:
+ The name of the script being run.
+
+ encoding(str)
+ The encoding for text, used by dogtail.tc.TCString .
+
+ actionDelay(float):
+ The delay after an action is executed.
+
+ typingDelay(float):
+ The delay after a character is typed on the keyboard.
+
+ runInterval(float):
+ The interval at which dogtail.utils.run() and dogtail.procedural.run()
+ check to see if the application has started up.
+
+ runTimeout(int):
+ The timeout after which dogtail.utils.run() and dogtail.procedural.run()
+ give up on looking for the newly-started application.
+
+ searchBackoffDuration (float):
+ Time in seconds for which to delay when a search fails.
+
+ searchWarningThreshold (int):
+ Number of retries before logging the individual attempts at a search.
+
+ searchCutoffCount (int):
+ Number of times to retry when a search fails.
+
+ defaultDelay (float):
+ Default time in seconds to sleep when delaying.
+
+ childrenLimit (int):
+ When there are a very large number of children of a node, only return
+ this many, starting with the first.
+
+ debugSearching (boolean):
+ Whether to write info on search backoff and retry to the debug log.
+
+ debugSleep (boolean):
+ Whether to log whenever we sleep to the debug log.
+
+ debugSearchPaths (boolean):
+ Whether we should write out debug info when running the SearchPath
+ routines.
+
+ absoluteNodePaths (boolean):
+ Whether we should identify nodes in the logs with long 'abcolute paths', or
+ merely with a short 'relative path'. FIXME: give examples
+
+ ensureSensitivity (boolean):
+ Should we check that ui nodes are sensitive (not 'greyed out') before
+ performing actions on them? If this is True (the default) it will raise
+ an exception if this happens. Can set to False as a workaround for apps
+ and toolkits that don't report sensitivity properly.
+
+ debugTranslation (boolean):
+ Whether we should write out debug information from the translation/i18n
+ subsystem.
+
+ blinkOnActions (boolean):
+ Whether we should blink a rectangle around a Node when an action is
+ performed on it.
+
+ fatalErrors (boolean):
+ Whether errors encountered in dogtail.procedural should be considered
+ fatal. If True, exceptions will be raised. If False, warnings will be
+ passed to the debug logger.
+
+ checkForA11y (boolean):
+ Whether to check if accessibility is enabled. If not, just assume it is
+ (default True).
+
+ logDebugToFile (boolean):
+ Whether to write debug output to a log file.
+
+ logDebugToStdOut (boolean):
+ Whether to print log output to console or not (default True).
+ """
+ @property
+ def scriptName(self):
+ return os.path.basename(sys.argv[0]).replace('.py', '')
+
+ @property
+ def encoding(self):
+ return locale.getpreferredencoding().lower()
+
+ defaults = {
+ # Storage
+ 'scratchDir': '/'.join((_userTmpDir('dogtail'), '')),
+ 'dataDir': '/'.join((_userTmpDir('dogtail'), 'data', '')),
+ 'logDir': '/'.join((_userTmpDir('dogtail'), 'logs', '')),
+ 'scriptName': scriptName.fget(None),
+ 'encoding': encoding.fget(None),
+ 'configFile': None,
+ 'baseFile': None,
+
+ # Timing and Limits
+ 'actionDelay': 1.0,
+ 'typingDelay': 0.1,
+ 'runInterval': 0.5,
+ 'runTimeout': 30,
+ 'searchBackoffDuration': 0.5,
+ 'searchWarningThreshold': 3,
+ 'searchCutoffCount': 20,
+ 'defaultDelay': 0.5,
+ 'childrenLimit': 100,
+
+ # Debug
+ 'debugSearching': False,
+ 'debugSleep': False,
+ 'debugSearchPaths': False,
+ 'logDebugToStdOut': True,
+ 'absoluteNodePaths': False,
+ 'ensureSensitivity': False,
+ 'debugTranslation': False,
+ 'blinkOnActions': False,
+ 'fatalErrors': False,
+ 'checkForA11y': True,
+
+ # Logging
+ 'logDebugToFile': True
+ }
+
+ options = {}
+
+ invalidValue = "__INVALID__"
+
+ def __init__(self):
+ _Config.__createDir(_Config.defaults['scratchDir'])
+ _Config.__createDir(_Config.defaults['logDir'])
+ _Config.__createDir(_Config.defaults['dataDir'])
+
+ def __setattr__(self, name, value):
+ if name not in config.defaults:
+ raise AttributeError(name + " is not a valid option.")
+
+ elif _Config.defaults[name] != value or \
+ _Config.options.get(name, _Config.invalidValue) != value:
+ if 'Dir' in name:
+ _Config.__createDir(value)
+ if value[-1] != os.path.sep:
+ value = value + os.path.sep
+ elif name == 'logDebugToFile':
+ import logging
+ logging.debugLogger = logging.Logger('debug', value)
+ _Config.options[name] = value
+
+ def __getattr__(self, name):
+ try:
+ return _Config.options[name]
+ except KeyError:
+ try:
+ return _Config.defaults[name]
+ except KeyError:
+ raise AttributeError("%s is not a valid option." % name)
+
+ def __createDir(cls, dirName, perms=0o777):
+ """
+ Creates a directory (if it doesn't currently exist), creating any
+ parent directories it needs.
+
+ If perms is None, create with python's default permissions.
+ """
+ dirName = os.path.abspath(dirName)
+ # print "Checking for %s ..." % dirName,
+ if not os.path.isdir(dirName):
+ if perms:
+ umask = os.umask(0)
+ os.makedirs(dirName, perms)
+ os.umask(umask)
+ else:
+ # This is probably a dead code - no other functions call this without the permissions set
+ os.makedirs(dirName) # pragma: no cover
+ __createDir = classmethod(__createDir)
+
+ def load(self, dict):
+ """
+ Loads values from dict, preserving any options already set that are not overridden.
+ """
+ _Config.options.update(dict)
+
+ def reset(self):
+ """
+ Resets all settings to their defaults.
+ """
+ _Config.options = {}
+
+
+config = _Config()
diff --git a/dogtail/config.pyc b/dogtail/config.pyc
new file mode 100644
index 00000000000..e9c4f40726c
--- /dev/null
+++ b/dogtail/config.pyc
Binary files differ
diff --git a/dogtail/distro.py b/dogtail/distro.py
new file mode 100755
index 00000000000..6f70f194650
--- /dev/null
+++ b/dogtail/distro.py
@@ -0,0 +1,362 @@
+"""Handles differences between different distributions
+
+Authors: Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Dave Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"
+
+import os
+import re
+from version import Version
+from logging import debugLogger as logger
+
+
+class DistributionNotSupportedError(Exception): # pragma: no cover
+
+ """
+ This distribution is not supported.
+ """
+ PATCH_MESSAGE = "Please send patches to dogtail-devel-list@gnome.org"
+
+ def __init__(self, distro):
+ self.distro = distro
+
+ def __str__(self):
+ return self.distro + ". " + DistributionNotSupportedError.PATCH_MESSAGE
+
+
+class PackageNotFoundError(Exception):
+
+ """
+ Error finding the requested package.
+ """
+ pass
+
+global packageDb
+global distro
+
+
+class PackageDb(object):
+
+ """
+ Class to abstract the details of whatever software package database is in
+ use (RPM, APT, etc)
+ """
+
+ def __init__(self):
+ self.prefix = '/usr'
+ self.localePrefixes = [self.prefix + '/share/locale']
+
+ def getVersion(self, packageName):
+ """
+ Method to get the version of an installed package as a Version
+ instance (or raise an exception if not found)
+
+ Note: does not know about distributions' internal revision numbers.
+ """
+ raise NotImplementedError
+
+ def getFiles(self, packageName):
+ """
+ Method to get a list of filenames owned by the package, or raise an
+ exception if not found.
+ """
+ raise NotImplementedError
+
+ def getMoFiles(self, locale=None):
+ """
+ Method to get a list of all .mo files on the system, optionally for a
+ specific locale.
+ """
+ moFiles = {}
+
+ def appendIfMoFile(moFiles, dirName, fNames):
+ import re
+ for fName in fNames:
+ if re.match('(.*)\\.mo', fName):
+ moFiles[dirName + '/' + fName] = None
+
+ for localePrefix in self.localePrefixes:
+ if locale:
+ localePrefix = localePrefix + '/' + locale
+ os.path.walk(localePrefix, appendIfMoFile, moFiles)
+
+ return moFiles.keys()
+
+ def getDependencies(self, packageName):
+ """
+ Method to get a list of unique package names that this package
+ is dependent on, or raise an exception if the package is not
+ found.
+ """
+ raise NotImplementedError
+
+
+class _RpmPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return Version.fromString(header["version"])
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ return header["filenames"]
+ raise PackageNotFoundError(packageName)
+
+ def getDependencies(self, packageName):
+ import rpm
+ ts = rpm.TransactionSet()
+ for header in ts.dbMatch("name", packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+
+ # Get the list of requirements; these are
+ # sometimes package names, but can also be
+ # so-names of libraries, and invented virtual
+ # ids
+ for requirement in header[rpm.RPMTAG_REQUIRES]:
+ # Get the name of the package providing
+ # this requirement:
+ for depPackageHeader in ts.dbMatch("provides", requirement):
+ depName = depPackageHeader['name']
+ if depName != packageName:
+ # Add to the Hash with a dummy value
+ result[depName] = None
+ return result.keys()
+ raise PackageNotFoundError(packageName)
+
+
+class _AptPackageDb(PackageDb):
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ self.cache = None
+
+ def getVersion(self, packageName):
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ verString = re.match(
+ '.*Ver:\'(.*)-.*\' Section:', str(package.CurrentVer)).group(1)
+ return Version.fromString(verString)
+ raise PackageNotFoundError(packageName)
+
+ def getFiles(self, packageName):
+ files = []
+ list = os.popen('dpkg -L %s' % packageName).readlines()
+ if not list:
+ raise PackageNotFoundError(packageName)
+ else:
+ for line in list:
+ file = line.strip()
+ if file:
+ files.append(file)
+ return files
+
+ def getDependencies(self, packageName):
+ # Simulate a set using a hash (to a dummy value);
+ # sets were only added in Python 2.4
+ result = {}
+ if not self.cache:
+ import apt_pkg
+ apt_pkg.init()
+ self.cache = apt_pkg.GetCache()
+ packages = self.cache.Packages
+ for package in packages:
+ if package.Name == packageName:
+ current = package.CurrentVer
+ if not current:
+ raise PackageNotFoundError(packageName)
+ depends = current.DependsList
+ list = depends['Depends']
+ for dependency in list:
+ name = dependency[0].TargetPkg.Name
+ # Add to the hash using a dummy value
+ result[name] = None
+ return result.keys()
+
+
+class _UbuntuAptPackageDb(_AptPackageDb):
+
+ def __init__(self):
+ _AptPackageDb.__init__(self)
+ self.localePrefixes.append(self.prefix + '/share/locale-langpack')
+
+
+class _PortagePackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ # the portage utilities are almost always going to be in
+ # /usr/lib/portage/pym
+ import sys
+ sys.path.append('/usr/lib/portage/pym')
+ import portage
+ # FIXME: this takes the first package returned in the list, in the
+ # case that there are slotted packages, and removes the leading
+ # category such as 'sys-apps'
+ gentooPackageName = portage.db["/"][
+ "vartree"].dbapi.match(packageName)[0].split('/')[1]
+ # this removes the distribution specific versioning returning only the
+ # upstream version
+ upstreamVersion = portage.pkgsplit(gentooPackageName)[1]
+ # print "Version of package is: " + upstreamVersion
+ return Version.fromString(upstreamVersion)
+
+
+class _ConaryPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+ def getVersion(self, packageName):
+ from conaryclient import ConaryClient
+ client = ConaryClient()
+ dbVersions = client.db.getTroveVersionList(packageName)
+ if not len(dbVersions):
+ raise PackageNotFoundError(packageName)
+ return dbVersions[0].trailingRevision().asString().split("-")[0]
+
+# getVersion not implemented because on Solaris multiple modules are installed
+# in single packages, so it is hard to tell what version number of a specific
+# module.
+
+
+class _SolarisPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+
+
+class JhBuildPackageDb(PackageDb): # pragma: no cover
+
+ def __init__(self):
+ PackageDb.__init__(self)
+ prefixes = []
+ prefixes.append(os.environ['LD_LIBRARY_PATH'])
+ prefixes.append(os.environ['XDG_CONFIG_DIRS'])
+ prefixes.append(os.environ['PKG_CONFIG_PATH'])
+ self.prefix = os.path.commonprefix(prefixes)
+ self.localePrefixes.append(self.prefix + '/share/locale')
+
+ def getDependencies(self, packageName):
+ result = {}
+ lines = os.popen('jhbuild list ' + packageName).readlines()
+ for line in lines:
+ if line:
+ result[line.strip()] = None
+ return result.keys()
+
+
+class Distro(object):
+
+ """
+ Class representing a distribution.
+
+ Scripts may want to do arbitrary logic based on whichever distro is in use
+ (e.g. handling differences in names of packages, distribution-specific
+ patches, etc.)
+
+ We can either create methods in the Distro class to handle these, or we
+ can use constructs like isinstance(distro, Ubuntu) to handle this. We can
+ even create hierarchies of distro subclasses to handle this kind of thing
+ (could get messy fast though)
+ """
+
+
+class Fedora(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class RHEL(Fedora): # pragma: no cover
+ pass
+
+
+class Debian(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _AptPackageDb()
+
+
+class Ubuntu(Debian):
+
+ def __init__(self):
+ self.packageDb = _UbuntuAptPackageDb()
+
+
+class Suse(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _RpmPackageDb()
+
+
+class Gentoo(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _PortagePackageDb()
+
+
+class Conary(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _ConaryPackageDb()
+
+
+class Solaris(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = _SolarisPackageDb()
+
+
+class JHBuild(Distro): # pragma: no cover
+
+ def __init__(self):
+ self.packageDb = JhBuildPackageDb()
+
+
+def detectDistro():
+ logger.log("Detecting distribution:", newline=False)
+
+ if os.environ.get("CERTIFIED_GNOMIE", "no") == "yes":
+ distro = JHBuild() # pragma: no cover
+ elif os.path.exists("/etc/SuSE-release"):
+ distro = Suse() # pragma: no cover
+ elif os.path.exists("/etc/fedora-release"):
+ distro = Fedora() # pragma: no cover
+ elif os.path.exists("/etc/redhat-release"):
+ distro = RHEL() # pragma: no cover
+ elif os.path.exists("/usr/share/doc/ubuntu-minimal"):
+ distro = Ubuntu()
+ elif os.path.exists("/etc/debian_version"): # pragma: no cover
+ distro = Debian() # pragma: no cover
+ elif os.path.exists("/etc/gentoo-release"): # pragma: no cover
+ distro = Gentoo() # pragma: no cover
+ elif os.path.exists("/etc/slackware-version"): # pragma: no cover
+ raise DistributionNotSupportedError("Slackware") # pragma: no cover
+ elif os.path.exists("/var/lib/conarydb/conarydb"): # pragma: no cover
+ distro = Conary() # pragma: no cover
+ elif os.path.exists("/etc/release") and \
+ re.match(".*Solaris", open("/etc/release").readline()): # pragma: no cover
+ distro = Solaris() # pragma: no cover
+ else:
+ raise DistributionNotSupportedError("Unknown") # pragma: no cover
+ logger.log(distro.__class__.__name__)
+ return distro
+
+distro = detectDistro()
+packageDb = distro.packageDb
diff --git a/dogtail/dump.py b/dogtail/dump.py
new file mode 100755
index 00000000000..3756820a510
--- /dev/null
+++ b/dogtail/dump.py
@@ -0,0 +1,33 @@
+"""Utility functions for 'dumping' trees of Node objects.
+
+Author: Zack Cerza <zcerza@redhat.com>"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from __builtin__ import file
+
+spacer = ' '
+
+
+def plain(node, fileName=None):
+ """
+ Plain-text dump. The hierarchy is represented through indentation.
+ """
+ def crawl(node, depth):
+ dump(node, depth)
+ for action in node.actions.values():
+ dump(action, depth + 1)
+ for child in node.children:
+ crawl(child, depth + 1)
+
+ def dumpFile(item, depth):
+ _file.write(spacer * depth + str(item) + '\n')
+
+ def dumpStdOut(item, depth):
+ print(spacer * depth + str(item))
+ if fileName:
+ dump = dumpFile
+ _file = file(fileName, 'w')
+ else:
+ dump = dumpStdOut
+
+ crawl(node, 0)
diff --git a/dogtail/errors.py b/dogtail/errors.py
new file mode 100755
index 00000000000..648af9d598e
--- /dev/null
+++ b/dogtail/errors.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+General exceptions; not overly module-specific
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+from logging import debugLogger as logger
+
+import inspect
+
+
+def warn(message, caller=True):
+ """
+ Generate a warning, and pass it to the debug logger.
+ """
+ frameRec = inspect.stack()[-1]
+ message = "Warning: %s:%s: %s" % (frameRec[1], frameRec[2], message)
+ if caller and frameRec[1] != '<stdin>' and frameRec[1] != '<string>':
+ message = message + ':\n ' + frameRec[4][0]
+ del frameRec
+ logger.log(message)
+
+
+class DependencyNotFoundError(Exception):
+
+ """
+ A dependency was not found.
+ """
+ pass
diff --git a/dogtail/i18n.py b/dogtail/i18n.py
new file mode 100755
index 00000000000..8117f8d82c6
--- /dev/null
+++ b/dogtail/i18n.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+"""
+Internationalization facilities
+
+Authors: David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>"""
+
+import config
+
+import os
+import re
+import gettext
+
+from logging import debugLogger as logger
+from __builtin__ import unicode
+
+
+def safeDecode(string):
+ try:
+ string = string.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ string = string.encode('utf-8', 'replace')
+ return string
+
+
+def safeEncode(string):
+ pass
+
+
+"""
+Singleton list of TranslationDb instances, to be initialized by the script with
+whatever translation databases it wants.
+"""
+translationDbs = []
+
+
+class TranslationDb(object):
+
+ """
+ Abstract base class representing a database of translations
+ """
+
+ def getTranslationsOf(self, srcName):
+ """
+ Pure virtual method to look up the translation of a string.
+ Returns a list of candidate strings (the translation), empty if not found.
+
+ Note that a source string can map to multiple translated strings. For
+ example, in the French translation of Evolution, the string "Forward" can
+ translate to both
+ (i) "Faire suivre" for forwarding an email, and
+ (ii) "Suivant" for the next page in a wizard.
+ """
+ raise NotImplementedError
+
+
+class GettextTranslationDb(TranslationDb):
+
+ """
+ Implementation of TranslationDb which leverages gettext, using a single
+ translation mo-file.
+ """
+
+ def __init__(self, moFile):
+ self.__moFile = moFile
+ self.__gnutranslations = gettext.GNUTranslations(open(moFile))
+
+ def getTranslationsOf(self, srcName):
+ srcName = safeDecode(srcName)
+ # print "searching for translations of %s"%srcName
+ # Use a dict to get uniqueness:
+ results = {}
+ result = self.__gnutranslations.ugettext(srcName)
+ if result != srcName:
+ results[result] = None
+
+ # Hack alert:
+ #
+ # Note that typical UI definition in GTK etc contains strings with
+ # underscores to denote accelerators.
+ # For example, the stock GTK "Add" item has text "_Add" which e.g.
+ # translates to "A_jouter" in French
+ #
+ # Since these underscores have been stripped out before we see these strings,
+ # we are looking for a translation of "Add" into "Ajouter" in this case, so
+ # we need to fake it, by looking up the string multiple times, with underscores
+ # inserted in all possible positions, stripping underscores out of the result.
+ # Ugly, but it works.
+
+ for index in range(len(srcName)):
+ candidate = srcName[:index] + "_" + srcName[index:]
+ result = self.__gnutranslations.ugettext(candidate)
+ if result != candidate:
+ # Strip out the underscore, and add to the result:
+ results[result.replace('_', '')] = True
+
+ return results.keys()
+
+
+def translate(srcString):
+ """
+ Look up srcString in the various translation databases (if any), returning
+ a list of all matches found (potentially the empty list)
+ """
+ # Use a dict to get uniqueness:
+ results = {}
+ # Try to translate the string:
+ for translationDb in translationDbs:
+ for result in translationDb.getTranslationsOf(srcString):
+ result = safeDecode(result)
+ results[result] = True
+
+ # No translations found:
+ if len(results) == 0:
+ if config.config.debugTranslation:
+ logger.log('Translation not found for "%s"' % srcString)
+ return results.keys()
+
+
+class TranslatableString(object):
+
+ """
+ Class representing a string that we want to match strings against, handling
+ translation for us, by looking it up once at construction time.
+ """
+
+ def __init__(self, untranslatedString):
+ """
+ Constructor looks up the string in all of the translation databases, storing
+ the various translations it finds.
+ """
+ untranslatedString = safeDecode(untranslatedString)
+ self.untranslatedString = untranslatedString
+ self.translatedStrings = translate(untranslatedString)
+
+ def matchedBy(self, string):
+ """
+ Compare the test string against either the translation of the original
+ string (or simply the original string, if no translation was found).
+ """
+ # print "comparing %s against %s"%(string, self)
+ def stringsMatch(inS, outS):
+ """
+ Compares a regular expression to a string
+
+ inS: the regular expression (or normal string)
+ outS: the normal string to be compared against
+ """
+ inString = str(inS)
+ outString = outS
+ if inString == outString:
+ return True
+ inString = inString + '$'
+ inString = safeDecode(inString)
+ outString = safeDecode(outString)
+ if inString[0] == '*':
+ inString = "\\" + inString
+ # Escape all parentheses, since grouping will never be needed here
+ inString = re.sub('([\(\)])', r'\\\1', inString)
+ match = re.match(inString, outString)
+ matched = match is not None
+ return matched
+
+ matched = False
+ # the 'ts' variable keeps track of whether we're working with
+ # translated strings. it's only used for debugging purposes.
+ #ts = 0
+ # print string, str(self)
+ for translatedString in self.translatedStrings:
+ #ts = ts + 1
+ matched = stringsMatch(translatedString, string)
+ if not matched:
+ matched = translatedString == string
+ if matched:
+ return matched
+ # ts=0
+ return stringsMatch(self.untranslatedString, string)
+
+ def __str__(self):
+ """
+ Provide a meaningful debug version of the string (and the translation in
+ use)
+ """
+ if len(self.translatedStrings) > 0:
+ # build an output string, with commas in the correct places
+ translations = ""
+ for tString in self.translatedStrings:
+ translations += u'"%s", ' % safeDecode(tString)
+ result = u'"%s" (%s)' % (
+ safeDecode(self.untranslatedString), translations)
+ return safeDecode(result)
+ else:
+ return '"%s"' % (self.untranslatedString)
+
+
+def isMoFile(filename, language=''):
+ """
+ Does the given filename look like a gettext mo file?
+
+ Optionally: Does the file also contain translations for a certain language,
+ for example 'ja'?
+ """
+ if re.match('(.*)\\.mo$', filename):
+ if not language:
+ return True
+ elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' %
+ language, filename):
+ return True
+ else:
+ return False
+ else:
+ return False
+
+
+def loadAllTranslationsForLanguage(language):
+ import distro
+ for moFile in distro.packageDb.getMoFiles(language):
+ translationDbs.append(GettextTranslationDb(moFile))
+
+
+def getMoFilesForPackage(packageName, language='', getDependencies=True):
+ """
+ Look up the named package and find all gettext mo files within it and its
+ dependencies. It is possible to restrict the results to those of a certain
+ language, for example 'ja'.
+ """
+ import distro
+
+ result = []
+ for filename in distro.packageDb.getFiles(packageName):
+ if isMoFile(filename, language):
+ result.append(filename)
+
+ if getDependencies:
+ # Recurse:
+ for dep in distro.packageDb.getDependencies(packageName):
+ # We pass False to the inner call because getDependencies has already
+ # walked the full tree
+ result.extend(getMoFilesForPackage(dep, language, False))
+
+ return result
+
+
+def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
+ """
+ Helper function which appends all of the gettext translation mo-files used by
+ the package (and its dependencies) to the translation database list.
+ """
+ # Keep a list of mo-files that are already in use to avoid duplicates.
+ moFiles = {}
+
+ def load(packageName, language='', getDependencies=True):
+ for moFile in getMoFilesForPackage(packageName, language, getDependencies):
+ # Searching the popt mo-files for translations makes gettext bail out,
+ # so we ignore them here. This is
+ # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 .
+ if not('popt.mo' in moFile or moFile in moFiles):
+ try:
+ translationDbs.append(GettextTranslationDb(moFile))
+ moFiles[moFile] = None
+ except (AttributeError, IndexError):
+ if config.config.debugTranslation:
+ #import traceback
+ # logger.log(traceback.format_exc())
+ logger.log(
+ "Warning: Failed to load mo-file for translation: " + moFile)
+
+ # Hack alert:
+ #
+ # The following special-case is necessary for Ubuntu, since their
+ # translations are shipped in a single huge package. The downside to
+ # this special case, aside from the simple fact that there is one,
+ # is that it makes automatic translations much slower.
+
+ import distro
+ language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2]
+ if isinstance(distro.distro, distro.Ubuntu):
+ load('language-pack-gnome-%s' % language, language)
+ load(packageName, language, getDependencies)
diff --git a/dogtail/i18n.pyc b/dogtail/i18n.pyc
new file mode 100644
index 00000000000..d999807462f
--- /dev/null
+++ b/dogtail/i18n.pyc
Binary files differ
diff --git a/dogtail/logging.py b/dogtail/logging.py
new file mode 100755
index 00000000000..7e73f163b83
--- /dev/null
+++ b/dogtail/logging.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+"""
+Logging facilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+import os
+import sys
+import time
+from config import config
+import codecs
+
+# Timestamp class for file logs
+
+
+class TimeStamp(object):
+
+ """
+ Generates timestamps tempfiles and log entries
+ """
+
+ def __init__(self):
+ self.now = "0"
+ self.timetup = time.localtime()
+
+ def zeroPad(self, int, width=2):
+ """
+ Pads an integer 'int' with zeroes, up to width 'width'.
+
+ Returns a string.
+
+ It will not truncate. If you call zeroPad(100, 2), '100' will be returned.
+ """
+ if int < 10 ** width:
+ return ("0" * (width - len(str(int)))) + str(int)
+ else:
+ return str(int)
+
+ # file stamper
+ def fileStamp(self, filename, addTime=True):
+ """
+ Generates a filename stamp in the format of filename_YYYYMMDD-hhmmss.
+ A format of filename_YYYYMMDD can be used instead by specifying addTime = False.
+ """
+ self.now = filename.strip() + "_"
+ self.timetup = time.localtime()
+
+ # Should produce rel-eng style filestamps
+ # format it all pretty by chopping the tuple
+ fieldCount = 3
+ if addTime:
+ fieldCount = fieldCount + 3
+ for i in range(fieldCount):
+ if i == 3:
+ self.now = self.now + '-'
+ self.now = self.now + self.zeroPad(self.timetup[i])
+ return self.now
+
+ # Log entry stamper
+ def entryStamp(self):
+ """
+ Generates a logfile entry stamp of YYYY.MM.DD HH:MM:SS
+ """
+ self.timetup = time.localtime()
+
+ # This will return a log entry formatted string in YYYY.MM.DD HH:MM:SS
+ for i in range(6):
+ # put in the year
+ if i == 0:
+ self.now = str(self.timetup[i])
+ # Format Month and Day
+ elif i == 1 or i == 2:
+ self.now = self.now + "." + self.zeroPad(self.timetup[i])
+ else:
+ # make the " " between Day and Hour and put in the hour
+ if i == 3:
+ self.now = self.now + " " + self.zeroPad(self.timetup[i])
+ # Otherwise Use the ":" divider
+ else:
+ self.now = self.now + ":" + self.zeroPad(self.timetup[i])
+ return self.now
+
+
+class Logger(object):
+
+ """
+ Writes entries to standard out.
+ """
+ stamper = TimeStamp()
+
+ def __init__(self, logName, file=False, stdOut=True):
+ """
+ name: the name of the log
+ file: The file object to log to.
+ stdOut: Whether to log to standard out.
+ """
+ self.logName = logName
+ self.stdOut = stdOut
+ self.file = file # Handle to the logfile
+ if not self.file:
+ return
+
+ scriptName = config.scriptName
+ if not scriptName:
+ scriptName = 'log'
+ self.fileName = scriptName
+
+ # check to see if we can write to the logDir
+ if os.path.isdir(config.logDir):
+ self.findUniqueName()
+ else:
+ # If path doesn't exist, raise an exception
+ raise IOError(
+ "Log path %s does not exist or is not a directory" % config.logDir)
+
+ def findUniqueName(self):
+ # generate a logfile name and check if it already exists
+ self.fileName = config.logDir + self.stamper.fileStamp(self.fileName) \
+ + '_' + self.logName
+ i = 0
+ while os.path.exists(self.fileName):
+ # Append the pathname
+ if i == 0:
+ self.fileName = self.fileName + "." + str(i)
+ else:
+ logsplit = self.fileName.split(".")
+ logsplit[-1] = str(i)
+ self.fileName = ".".join(logsplit)
+ i += 1
+
+ def createFile(self):
+ # Try to create the file and write the header info
+ print("Creating logfile at %s ..." % self.fileName)
+ self.file = codecs.open(self.fileName, mode='wb', encoding=
+ 'utf-8')
+ self.file.write("##### " + os.path.basename(self.fileName) + '\n')
+ self.file.flush()
+
+ def log(self, message, newline=True, force=False):
+ """
+ Hook used for logging messages. Might eventually be a virtual
+ function, but nice and simple for now.
+
+ If force is True, log to a file irrespective of config.logDebugToFile.
+ """
+ try:
+ message = message.decode('utf-8', 'replace')
+ except UnicodeEncodeError:
+ pass
+
+
+ # Try to open and write the result to the log file.
+ if isinstance(self.file, bool) and (force or config.logDebugToFile):
+ self.createFile()
+
+ if force or config.logDebugToFile:
+ if newline:
+ self.file.write(message + '\n')
+ else:
+ self.file.write(message + ' ')
+ self.file.flush()
+
+ if self.stdOut and config.logDebugToStdOut:
+ if newline:
+ print(message)
+ else:
+ print(message)
+
+
+class ResultsLogger(Logger):
+
+ """
+ Writes entries into the Dogtail log
+ """
+
+ def __init__(self, stdOut=True):
+ Logger.__init__(self, 'results', file=True, stdOut=stdOut)
+
+ # Writes the result of a test case comparison to the log
+ def log(self, entry):
+ """
+ Writes the log entry. Requires a 1 {key: value} pair dict for an argument or else it will throw an exception.
+ """
+ # We require a 1 key: value dict
+ # Strip all leading and trailing witespace from entry dict and convert
+ # to string for writing
+
+ if len(entry) == 1:
+ key = entry.keys()
+ value = entry.values()
+ key = key[0]
+ value = value[0]
+ entry = str(key) + ": " + str(value)
+ else:
+ raise ValueError(entry)
+ print(
+ "Method argument requires a 1 {key: value} dict. Supplied argument not one {key: value}")
+
+ Logger.log(self, self.stamper.entryStamp() + " " + entry,
+ force=True)
+
+debugLogger = Logger('debug', config.logDebugToFile)
+
+import traceback
+
+
+def exceptionHook(exc, value, tb): # pragma: no cover
+ tbStringList = traceback.format_exception(exc, value, tb)
+ tbString = ''.join(tbStringList)
+ debugLogger.log(tbString)
+ sys.exc_clear()
+
+sys.excepthook = exceptionHook
diff --git a/dogtail/logging.pyc b/dogtail/logging.pyc
new file mode 100644
index 00000000000..9b01a4b4cc1
--- /dev/null
+++ b/dogtail/logging.pyc
Binary files differ
diff --git a/dogtail/path.py b/dogtail/path.py
new file mode 100755
index 00000000000..5742cfb03e6
--- /dev/null
+++ b/dogtail/path.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""
+Author: David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """David Malcolm <dmalcolm@redhat.com>"""
+
+
+class SearchPath(object):
+
+ """
+ Class used by the recording framework (and for more verbose script
+ logging) for identifying nodes in a persistent way, independent of the
+ style of script being written.
+
+ Implemented as a list of (predicate, isRecursive) pairs, giving the
+ 'best' way to find the Accessible wrapped by a Node, starting at the
+ root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem, except
+ that some of searches may be recursive, rather than just searching
+ direct children.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+
+ def __init__(self):
+ self.__list = []
+
+ def __str__(self):
+ result = "{"
+ for (predicate, isRecursive) in self.__list:
+ result += "/(%s,%s)" % (
+ predicate.describeSearchResult(), isRecursive)
+ return result + "}"
+
+ # We need equality to work so that dicts of these work:
+ def __eq__(self, other):
+ # print "eq: self:%s"%self
+ # print " other:%s"%other
+ if len(self.__list) != len(other.__list):
+ # print "nonequal length"
+ return False
+ else:
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ return False
+ # print True
+ return True
+
+ def append(self, predicate, isRecursive):
+ assert predicate
+ self.__list.append((predicate, isRecursive))
+
+ def __iter__(self):
+ return iter(self.__list)
+
+ def length(self):
+ return len(self.__list)
+
+ def makeScriptMethodCall(self):
+ """
+ Used by the recording system.
+
+ Generate the Python source code that will carry out this search.
+ """
+ result = ""
+ for (predicate, isRecursive) in self.__list:
+ # print predicate
+ # print self.generateVariableName(predicate)
+ result += "." + predicate.makeScriptMethodCall(isRecursive)
+ return result
+
+ def getRelativePath(self, other):
+ """
+ Given another SearchPath instance, if the other is 'below' this
+ one, return a SearchPath that describes how to reach it relative
+ to this one (a copy of the second part of the list). Otherwise
+ return None.
+ """
+ for i in range(len(self.__list)):
+ if self.__list[i] != other.__list[i]:
+ break
+ if i > 0:
+ # Slice from this point to the end:
+ result = SearchPath()
+ result.__list = other.__list[i + 1:]
+
+ if False:
+ print("....................")
+ print("from %s" % self)
+ print("to %s" % other)
+ print("i=%s" % i)
+ print("relative path %s" % result)
+ print("....................")
+
+ return result
+ else:
+ return None
+
+ def getPrefix(self, n):
+ """
+ Get the first n components of this instance as a new instance
+ """
+ result = SearchPath()
+ for i in range(n):
+ result.__list.append(self.__list[i])
+ return result
+
+ def getPredicate(self, i):
+ (predicate, isRecursive) = self.__list[i]
+ return predicate
diff --git a/dogtail/path.pyc b/dogtail/path.pyc
new file mode 100644
index 00000000000..610858fbd0c
--- /dev/null
+++ b/dogtail/path.pyc
Binary files differ
diff --git a/dogtail/predicate.py b/dogtail/predicate.py
new file mode 100755
index 00000000000..aa100e1f054
--- /dev/null
+++ b/dogtail/predicate.py
@@ -0,0 +1,443 @@
+"""Predicates that can be used when searching for nodes.
+
+Author: David Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'David Malcolm <dmalcolm@redhat.com>'
+
+from i18n import TranslatableString
+from gi.repository import GLib
+from time import sleep
+from logging import debugLogger as logger
+from config import config
+
+def stringMatches(scriptName, reportedName):
+ assert isinstance(scriptName, TranslatableString)
+
+ return scriptName.matchedBy(reportedName)
+
+
+def makeScriptRecursiveArgument(isRecursive, defaultValue):
+ if isRecursive == defaultValue:
+ return ""
+ else:
+ return ", recursive=%s" % isRecursive
+
+
+def makeCamel(string):
+ """
+ Convert string to camelCaps
+ """
+ string = str(string)
+ # FIXME: this function is probably really fragile, lots of difficult cases
+ # here
+
+ # Sanitize string, replacing bad characters with spaces:
+ for char in ":;!@#$%^&*()-+=_~`\\/?|[]{}<>,.\t\n\r\"'":
+ string = string.replace(char, " ")
+ words = string.strip().split(" ")
+ for word in words:
+ word.strip
+ result = ""
+ firstWord = True
+ for word in words:
+ lowercaseWord = word.lower()
+ if firstWord:
+ result += lowercaseWord
+ firstWord = False
+ else:
+ result += lowercaseWord.capitalize()
+ return result
+
+
+class Predicate(object):
+
+ """Abstract base class representing a predicate function on nodes.
+
+ It's more than just a function in that it has data and can describe itself"""
+
+ def satisfiedByNode(self, node):
+ """Pure virtual method returning a boolean if the predicate is satisfied by the node"""
+ raise NotImplementedError
+
+ def describeSearchResult(self, node):
+ raise NotImplementedError
+
+ def makeScriptMethodCall(self, isRecursive):
+ """
+ Method to generate a string containing a (hopefully) readable search
+ method call on a node (to be used when generating Python source code in
+ the event recorder)
+ """
+ raise NotImplementedError
+
+ def makeScriptVariableName(self):
+ """
+ Method to generate a string containing a (hopefully) readable name
+ for a Node instance variable that would be the result of a search on
+ this predicate (to be used when generating Python source code in the
+ event recorder).
+ """
+ raise NotImplementedError
+
+ def __eq__(self, other):
+ """
+ Predicates are considered equal if they are of the same subclass and
+ have the same data
+ """
+ # print "predeq: self:%s"%self
+ # print " other:%s"%other
+ # print "predeq: selfdict:%s"%self.__dict__
+ # print " otherdict:%s"%other.__dict__
+
+ if type(self) != type(other):
+ return False
+ else:
+ return self.__dict__ == other.__dict__
+
+
+class IsAnApplicationNamed(Predicate):
+
+ """Search subclass that looks for an application by name"""
+
+ def __init__(self, appName):
+ self.appName = TranslatableString(appName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ try:
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ try:
+ sleep(config.defaults['searchWarningThreshold'])
+ return node.roleName == 'application' and stringMatches(self.appName, node.name)
+ except GLib.GError:
+ logger.log("Dogtail: warning: application may be hanging")
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s application' % self.appName
+
+ def makeScriptMethodCall(self, isRecursive):
+ # ignores the isRecursive parameter
+ return "application(%s)" % self.appName
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.appName) + "App"
+
+
+class GenericPredicate(Predicate):
+
+ """SubtreePredicate subclass that takes various optional search fields"""
+
+ def __init__(self, name=None, roleName=None, description=None, label=None, debugName=None):
+ if name:
+ self.name = TranslatableString(name)
+ else:
+ self.name = None
+ self.roleName = roleName
+ self.description = description
+ if label:
+ self.label = TranslatableString(label)
+ else:
+ self.label = None
+
+ if debugName:
+ self.debugName = debugName
+ else:
+ if label:
+ self.debugName = "labelled '%s'" % self.label
+ else:
+ self.debugName = "child with"
+ if name:
+ self.debugName += " name=%s" % self.name
+ if roleName:
+ self.debugName += " roleName='%s'" % roleName
+ if description:
+ self.debugName += " description='%s'" % description
+ assert self.debugName
+
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # labelled nodes are handled specially:
+ if self.label:
+ # this reverses the search; we're looking for a node with LABELLED_BY
+ # and then checking the label, rather than looking for a label and
+ # then returning whatever LABEL_FOR targets
+ if node.labeller:
+ return stringMatches(self.label, node.labeller.name)
+ else:
+ return False
+ else:
+ # Ensure the node matches any criteria that were set:
+ try:
+ if self.name:
+ if not stringMatches(self.name, node.name):
+ return False
+ if self.roleName:
+ if self.roleName != node.roleName:
+ return False
+ if self.description:
+ if self.description != node.description:
+ return False
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ logger.log("Dogtail: warning: omiting possibly broken at-spi application record")
+ return False
+ else:
+ raise e
+ return True
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return self.debugName
+
+ def makeScriptMethodCall(self, isRecursive):
+ if self.label:
+ args = "label=%s" % self.label
+ else:
+ args = ""
+ if self.name:
+ print(self.name)
+ args += " name=%s" % self.name
+ if self.roleName:
+ args += " roleName='%s'" % self.roleName
+ if self.description:
+ args += " description='%s'" % self.description
+ return "child(%s%s)" % (args, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ if self.label:
+ return makeCamel(self.label) + "Node"
+ else:
+ if self.name:
+ return makeCamel(self.name) + "Node"
+ if self.roleName:
+ return makeCamel(self.roleName) + "Node"
+ if self.description:
+ return makeCamel(self.description) + "Node"
+
+
+class IsNamed(Predicate):
+
+ """Predicate subclass that looks simply by name"""
+
+ def __init__(self, name):
+ self.name = TranslatableString(name)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return stringMatches(self.name, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "named %s" % self.name
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(name=%s%s)" % (self.name, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.name) + "Node"
+
+
+class IsAWindowNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level window by name"""
+
+ def __init__(self, windowName):
+ self.windowName = TranslatableString(windowName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'frame' and stringMatches(self.windowName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return "%s window" % self.windowName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "window(%s%s)" % (self.windowName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.windowName) + "Win"
+
+
+class IsAWindow(Predicate):
+
+ """Predicate subclass that looks for top-level windows"""
+
+ def __init__(self):
+ self.satisfiedByNode = lambda node: node.roleName == 'frame'
+
+ def describeSearchResult(self):
+ return "window"
+
+
+class IsADialogNamed(Predicate):
+
+ """Predicate subclass that looks for a top-level dialog by name"""
+
+ def __init__(self, dialogName):
+ self.dialogName = TranslatableString(dialogName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ return node.roleName == 'dialog' and stringMatches(self.dialogName, node.name)
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return '%s dialog' % self.dialogName
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "dialog(%s%s)" % (self.dialogName, makeScriptRecursiveArgument(isRecursive, False))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.dialogName) + "Dlg"
+
+
+class IsLabelledBy(Predicate):
+
+ """Predicate: is this node labelled by another node"""
+ pass
+
+
+class IsLabelledAs(Predicate):
+
+ """Predicate: is this node labelled with the text string (i.e. by another node with that as a name)"""
+
+ def __init__(self, labelText):
+ self.labelText = TranslatableString(labelText)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = self._genCompareFunc()
+
+ def _genCompareFunc(self):
+ def satisfiedByNode(node):
+ # FIXME
+ if node.labeller:
+ return stringMatches(self.labelText, node.labeller.name)
+ else:
+ return False
+ return satisfiedByNode
+
+ def describeSearchResult(self):
+ return 'labelled %s' % self.labelText
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "child(label=%s%s)" % (self.labelText, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.labelText) + "Node"
+
+
+class IsAMenuNamed(Predicate):
+
+ """Predicate subclass that looks for a menu by name"""
+
+ def __init__(self, menuName):
+ self.menuName = TranslatableString(menuName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'menu' and \
+ stringMatches(self.menuName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menu' % (self.menuName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menu(%s%s)" % (self.menuName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuName) + "Menu"
+
+
+class IsAMenuItemNamed(Predicate):
+
+ """Predicate subclass that looks for a menu item by name"""
+
+ def __init__(self, menuItemName):
+ self.menuItemName = TranslatableString(menuItemName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: \
+ node.roleName.endswith('menu item') and \
+ stringMatches(self.menuItemName, node.name)
+
+ def describeSearchResult(self):
+ return '%s menuitem' % (self.menuItemName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "menuItem(%s%s)" % (self.menuItemName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.menuItemName) + "MenuItem"
+
+
+class IsATextEntryNamed(Predicate):
+
+ """Predicate subclass that looks for a text entry by name"""
+
+ def __init__(self, textEntryName):
+ self.textEntryName = TranslatableString(textEntryName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'text' and \
+ stringMatches(self.textEntryName, node.name)
+
+ def describeSearchResult(self):
+ return '%s textentry' % (self.textEntryName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "textentry(%s%s)" % (self.textEntryName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.textEntryName) + "Entry"
+
+
+class IsAButtonNamed(Predicate):
+
+ """Predicate subclass that looks for a button by name"""
+
+ def __init__(self, buttonName):
+ self.buttonName = TranslatableString(buttonName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'push button' \
+ and stringMatches(self.buttonName, node.name)
+
+ def describeSearchResult(self):
+ return '%s button' % (self.buttonName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "button(%s%s)" % (self.buttonName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.buttonName) + "Button"
+
+
+class IsATabNamed(Predicate):
+
+ """Predicate subclass that looks for a tab by name"""
+
+ def __init__(self, tabName):
+ self.tabName = TranslatableString(tabName)
+ self.debugName = self.describeSearchResult()
+ self.satisfiedByNode = lambda node: node.roleName == 'page tab' and \
+ stringMatches(self.tabName, node.name)
+
+ def describeSearchResult(self):
+ return '%s tab' % (self.tabName)
+
+ def makeScriptMethodCall(self, isRecursive):
+ return "tab(%s%s)" % (self.tabName, makeScriptRecursiveArgument(isRecursive, True))
+
+ def makeScriptVariableName(self):
+ return makeCamel(self.tabName) + "Tab"
diff --git a/dogtail/predicate.pyc b/dogtail/predicate.pyc
new file mode 100644
index 00000000000..f72e74c1053
--- /dev/null
+++ b/dogtail/predicate.pyc
Binary files differ
diff --git a/dogtail/procedural.py b/dogtail/procedural.py
new file mode 100755
index 00000000000..c87ae86181c
--- /dev/null
+++ b/dogtail/procedural.py
@@ -0,0 +1,455 @@
+"""
+Dogtail's procedural UI
+All the classes here are intended to be single-instance, except for Action.
+"""
+__author__ = 'Zack Cerza <zcerza@redhat.com>'
+#
+#
+# WARNING: Here There Be Dragons (TM) #
+#
+# If you don't understand how to use this API, you almost certainly don't #
+# want to read the code first. We make use of some very non-intuitive #
+# features of Python in order to make the API very simplistic. Therefore, #
+# you should probably only read this code if you're already familiar with #
+# some of Python's advanced features. You have been warned. ;) #
+#
+#
+
+import tree
+import predicate
+from config import config
+from utils import Lock
+import rawinput
+
+#FocusError = "FocusError: %s not found"
+
+
+class FocusError(Exception):
+ pass
+
+import errors
+
+
+def focusFailed(pred):
+ errors.warn('The requested widget could not be focused: %s' %
+ pred.debugName)
+
+ENOARGS = "At least one argument is needed"
+
+
+class FocusBase(object):
+
+ """
+ The base for every class in the module. Does nothing special, really.
+ """
+ node = None
+
+ def __getattr__(self, name):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ try:
+ return getattr(self.node, name)
+ except AttributeError:
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ # Fold all the Node's AT-SPI properties into the Focus object.
+ if name == 'node':
+ setattr(self.__class__, name, value)
+ else:
+ try:
+ setattr(self.node, name, value)
+ except AttributeError:
+ raise AttributeError(name)
+
+
+class FocusApplication (FocusBase):
+
+ """
+ Keeps track of which application is currently focused.
+ """
+ desktop = tree.root
+
+ def __call__(self, name):
+ """
+ Search for an application that matches and refocus on the given name.
+ """
+ try:
+ pred = predicate.IsAnApplicationNamed(name)
+ app = self.desktop.findChild(
+ pred, recursive=False, retry=False)
+ except tree.SearchError:
+ if config.fatalErrors:
+ raise FocusError(name)
+ else:
+ focusFailed(pred)
+ return False
+ if app:
+ FocusApplication.node = app
+ FocusDialog.node = None
+ FocusWindow.node = None
+ FocusWidget.node = None
+ return True
+
+
+class FocusDesktop (FocusBase):
+
+ """
+ This isn't used yet, and may never be used.
+ """
+ pass
+
+
+class FocusWindow (FocusBase):
+
+ """
+ Keeps track of which window is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsAWindowNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWindow.node = result
+ FocusDialog.node = None
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusDialog (FocusBase):
+
+ """
+ Keeps track of which dialog is currently focused.
+ """
+
+ def __call__(self, name):
+ """
+ Search for a dialog that matches the given name and refocus on it.
+ """
+ result = None
+ pred = predicate.IsADialogNamed(name)
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, recursive=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusDialog.node = result
+ FocusWidget.node = None
+ else:
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+
+class FocusWidget (FocusBase):
+
+ """
+ Keeps track of which widget is currently focused.
+ """
+
+ def findByPredicate(self, pred):
+ result = None
+ try:
+ result = FocusWidget.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusDialog.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusWindow.node.findChild(
+ pred, requireResult=False, retry=False)
+ except AttributeError:
+ pass
+ if result:
+ FocusWidget.node = result
+ else:
+ try:
+ result = FocusApplication.node.findChild(
+ pred, requireResult=False, retry=False)
+ if result:
+ FocusWidget.node = result
+ except AttributeError:
+ if config.fatalErrors:
+ raise FocusError(pred)
+ else:
+ focusFailed(pred)
+ return False
+
+ if result is None:
+ FocusWidget.node = result
+ if config.fatalErrors:
+ raise FocusError(pred.debugName)
+ else:
+ focusFailed(pred)
+ return False
+ return True
+
+ def __call__(self, name='', roleName='', description=''):
+ """
+ If name, roleName or description are specified, search for a widget that matches and refocus on it.
+ """
+ if not name and not roleName and not description:
+ raise TypeError(ENOARGS)
+
+ # search for a widget.
+ pred = predicate.GenericPredicate(name=name,
+ roleName=roleName, description=description)
+ return self.findByPredicate(pred)
+
+
+class Focus (FocusBase):
+
+ """
+ The container class for the focused application, dialog and widget.
+ """
+
+ def __getattr__(self, name):
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ if name in ('application', 'dialog', 'widget', 'window'):
+ self.__dict__[name] = value
+ else:
+ raise AttributeError(name)
+
+ desktop = tree.root
+ application = FocusApplication()
+ app = application # shortcut :)
+ dialog = FocusDialog()
+ window = FocusWindow()
+ frame = window
+ widget = FocusWidget()
+
+ def button(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAButtonNamed(name))
+
+ def icon(self, name):
+ """
+ A shortcut to self.widget(name, roleName = 'icon')
+ """
+ return self.widget(name=name, roleName='icon')
+
+ def menu(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuNamed(name))
+
+ def menuItem(self, name):
+ """
+ A shortcut to self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsAMenuItemNamed(name))
+
+ def table(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table')
+ """
+ return self.widget(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self.widget(name, roleName 'table cell')
+ """
+ return self.widget(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self.widget.findByPredicate(IsATextEntryNamed(name))
+ """
+ return self.widget.findByPredicate(predicate.IsATextEntryNamed(name))
+
+
+class Action (FocusWidget):
+
+ """
+ Aids in executing AT-SPI actions, refocusing the widget if necessary.
+ """
+
+ def __init__(self, action):
+ """
+ action is a string with the same name as the AT-SPI action you wish to execute using this class.
+ """
+ self.action = action
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ self.node.doActionNamed(self.action)
+
+ def __getattr__(self, attr):
+ return getattr(FocusWidget.node, attr)
+
+ def __setattr__(self, attr, value):
+ if attr == 'action':
+ self.__dict__[attr] = value
+ else:
+ setattr(FocusWidget, attr, value)
+
+ def button(self, name):
+ """
+ A shortcut to self(name, roleName = 'push button')
+ """
+ self.__call__(name=name, roleName='push button')
+
+ def menu(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu')
+ """
+ self.__call__(name=name, roleName='menu')
+
+ def menuItem(self, name):
+ """
+ A shortcut to self(name, roleName = 'menu item')
+ """
+ self.__call__(name=name, roleName='menu item')
+
+ def table(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table')
+ """
+ self.__call__(name=name, roleName='table')
+
+ def tableCell(self, name=''):
+ """
+ A shortcut to self(name, roleName 'table cell')
+ """
+ self.__call__(name=name, roleName='table cell')
+
+ def text(self, name=''):
+ """
+ A shortcut to self(name, roleName = 'text')
+ """
+ self.__call__(name=name, roleName='text')
+
+
+class Click (Action):
+
+ """
+ A special case of Action, Click will eventually handle raw mouse events.
+ """
+ primary = 1
+ middle = 2
+ secondary = 3
+
+ def __init__(self):
+ Action.__init__(self, 'click')
+
+ def __call__(self, name='', roleName='', description='', raw=True, button=primary, delay=config.actionDelay):
+ """
+ By default, execute a raw mouse event.
+ If raw is False or if button evaluates to False, just pass the rest of
+ the arguments to Action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ if raw and button:
+ # We're doing a raw mouse click
+ Click.node.click(button)
+ else:
+ Action.__call__(
+ self, name=name, roleName=roleName, description=description, delay=delay)
+
+
+class Select (Action):
+
+ """
+ Aids in selecting and deselecting widgets, i.e. page tabs
+ """
+ select = 'select'
+ deselect = 'deselect'
+
+ def __init__(self, action):
+ """
+ action must be 'select' or 'deselect'.
+ """
+ if action not in (self.select, self.deselect):
+ raise ValueError(action)
+ Action.__init__(self, action)
+
+ def __call__(self, name='', roleName='', description='', delay=config.actionDelay):
+ """
+ If name, roleName or description are specified, first search for a widget that matches and refocus on it.
+ Then execute the action.
+ """
+ if name or roleName or description:
+ FocusWidget.__call__(
+ self, name=name, roleName=roleName, description=description)
+ func = getattr(self.node, self.action)
+ func()
+
+
+def type(text):
+ if focus.widget.node:
+ focus.widget.node.typeText(text)
+ else:
+ rawinput.typeText(text)
+
+
+def keyCombo(combo):
+ if focus.widget.node:
+ focus.widget.node.keyCombo(combo)
+ else:
+ rawinput.keyCombo(combo)
+
+
+def run(application, arguments='', appName=''):
+ from utils import run as utilsRun
+ pid = utilsRun(application + ' ' + arguments, appName=appName)
+ focus.application(application)
+ return pid
+
+import os
+# tell sniff not to use auto-refresh while script using this module is running
+# may have already been locked by dogtail.tree
+if not os.path.exists('/tmp/sniff_refresh.lock'): # pragma: no cover
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError:
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+focus = Focus()
+click = Click()
+activate = Action('activate')
+openItem = Action('open')
+menu = Action('menu')
+select = Select(Select.select)
+deselect = Select(Select.deselect)
diff --git a/dogtail/rawinput.py b/dogtail/rawinput.py
new file mode 100755
index 00000000000..7100b29bfae
--- /dev/null
+++ b/dogtail/rawinput.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+"""
+Handles raw input using AT-SPI event generation.
+
+Note: Think of keyvals as keysyms, and keynames as keystrings.
+
+Authors: David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>
+"""
+
+__author__ = """
+David Malcolm <dmalcolm@redhat.com>,
+Zack Cerza <zcerza@redhat.com>
+"""
+import gi
+
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gdk
+from config import config
+from utils import doDelay
+from logging import debugLogger as logger
+from pyatspi import Registry as registry
+from pyatspi import (KEY_SYM, KEY_PRESS, KEY_PRESSRELEASE, KEY_RELEASE)
+from exceptions import ValueError
+from __builtin__ import unicode, unichr
+
+
+def doTypingDelay():
+ doDelay(config.typingDelay)
+
+
+def checkCoordinates(x, y):
+ if x < 0 or y < 0:
+ raise ValueError(
+ "Attempting to generate a mouse event at negative coordinates: (%s,%s)" % (x, y))
+
+
+def click(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s click at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sc' % button)
+ doDelay(config.actionDelay)
+
+
+def doubleClick(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button double-click at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s doubleclick at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sd' % button)
+ doDelay()
+
+
+def press(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button press at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s press at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sp' % button)
+ doDelay()
+
+
+def release(x, y, button=1, check=True):
+ """
+ Synthesize a mouse button release at (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse button %s release at (%s,%s)" % (button, x, y))
+ registry.generateMouseEvent(x, y, 'b%sr' % button)
+ doDelay()
+
+
+def absoluteMotion(x, y, mouseDelay=None, check=True):
+ """
+ Synthesize mouse absolute motion to (x,y)
+ """
+ if check:
+ checkCoordinates(x, y)
+ logger.log("Mouse absolute motion to (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def relativeMotion(x, y, mouseDelay=None):
+ logger.log("Mouse relative motion of (%s,%s)" % (x, y))
+ registry.generateMouseEvent(x, y, 'rel')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+
+def drag(fromXY, toXY, button=1, check=True):
+ """
+ Synthesize a mouse press, drag, and release on the screen.
+ """
+ logger.log("Mouse button %s drag from %s to %s" % (button, fromXY, toXY))
+
+ (x, y) = fromXY
+ press(x, y, button, check)
+ # doDelay()
+
+ (x, y) = toXY
+ absoluteMotion(x, y, check=check)
+ doDelay()
+
+ release(x, y, button, check)
+ doDelay()
+
+
+def typeText(string):
+ """
+ Types the specified string, one character at a time.
+ Please note, you may have to set a higher typing delay,
+ if your machine misses/switches the characters typed.
+ Needed sometimes on slow setups/VMs typing non-ASCII utf8 chars.
+ """
+ if not isinstance(string, unicode):
+ string = string.decode('utf-8')
+ for char in string:
+ pressKey(char)
+
+keyNameAliases = {
+ 'enter': 'Return',
+ 'esc': 'Escape',
+ 'alt': 'Alt_L',
+ 'control': 'Control_L',
+ 'ctrl': 'Control_L',
+ 'shift': 'Shift_L',
+ 'del': 'Delete',
+ 'ins': 'Insert',
+ 'pageup': 'Page_Up',
+ 'pagedown': 'Page_Down',
+ ' ': 'space',
+ '\t': 'Tab',
+ '\n': 'Return'
+}
+
+
+# TODO: Dead code
+def keySymToUniChar(keySym): # pragma: no cover
+ i = Gdk.keyval_to_unicode(keySym)
+ if i:
+ UniChar = unichr(i)
+ else:
+ UniChar = ''
+ return UniChar
+
+
+def uniCharToKeySym(uniChar):
+ # OK, if it's not actually unicode we can fix that, right?
+ if not isinstance(uniChar, unicode):
+ uniChar = unicode(uniChar, 'utf-8')
+ i = ord(uniChar)
+ keySym = Gdk.unicode_to_keyval(i)
+ return keySym
+
+
+# dead code
+def keySymToKeyName(keySym): # pragma: no cover
+ return Gdk.keyval_name(keySym)
+
+
+def keyNameToKeySym(keyName):
+ keyName = keyNameAliases.get(keyName.lower(), keyName)
+ keySym = Gdk.keyval_from_name(keyName)
+ # various error 'codes' returned for non-recognized chars in versions of GTK3.X
+ if keySym == 0xffffff or keySym == 0x0 or keySym is None:
+ try:
+ keySym = uniCharToKeySym(keyName)
+ except: # not even valid utf-8 char
+ try: # Last attempt run at a keyName ('Meta_L', 'Dash' ...)
+ keySym = getattr(Gdk, 'KEY_' + keyName)
+ except AttributeError:
+ raise KeyError(keyName)
+ return keySym
+
+
+def keyNameToKeyCode(keyName):
+ """
+ Use GDK to get the keycode for a given keystring.
+
+ Note that the keycode returned by this function is often incorrect when
+ the requested keystring is obtained by holding down the Shift key.
+
+ Generally you should use uniCharToKeySym() and should only need this
+ function for nonprintable keys anyway.
+ """
+ keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default())
+ entries = keymap.get_entries_for_keyval(
+ Gdk.keyval_from_name(keyName))
+ try:
+ return entries[1][0].keycode
+ except TypeError:
+ pass
+
+
+def pressKey(keyName):
+ """
+ Presses (and releases) the key specified by keyName.
+ keyName is the English name of the key as seen on the keyboard. Ex: 'enter'
+ Names are looked up in Gdk.KEY_ If they are not found there, they are
+ looked up by uniCharToKeySym().
+ """
+ keySym = keyNameToKeySym(keyName)
+ registry.generateKeyboardEvent(keySym, None, KEY_SYM)
+ doTypingDelay()
+
+
+def keyCombo(comboString):
+ """
+ Generates the appropriate keyboard events to simulate a user pressing the
+ specified key combination.
+
+ comboString is the representation of the key combo to be generated.
+ e.g. '<Control><Alt>p' or '<Control><Shift>PageUp' or '<Control>q'
+ """
+ strings = []
+ for s in comboString.split('<'):
+ if s:
+ for S in s.split('>'):
+ if S:
+ S = keyNameAliases.get(S.lower(), S)
+ strings.append(S)
+ for s in strings:
+ if not hasattr(Gdk, s):
+ if not hasattr(Gdk, 'KEY_' + s):
+ raise ValueError("Cannot find key %s" % s)
+ modifiers = strings[:-1]
+ finalKey = strings[-1]
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_PRESS)
+ code = keyNameToKeyCode(finalKey)
+ registry.generateKeyboardEvent(code, None, KEY_PRESSRELEASE)
+ for modifier in modifiers:
+ code = keyNameToKeyCode(modifier)
+ registry.generateKeyboardEvent(code, None, KEY_RELEASE)
+ doDelay()
diff --git a/dogtail/rawinput.pyc b/dogtail/rawinput.pyc
new file mode 100644
index 00000000000..0f4b881fec8
--- /dev/null
+++ b/dogtail/rawinput.pyc
Binary files differ
diff --git a/dogtail/sessions.py b/dogtail/sessions.py
new file mode 100755
index 00000000000..8d4bcce6a37
--- /dev/null
+++ b/dogtail/sessions.py
@@ -0,0 +1,231 @@
+import time
+import os
+import pwd
+import errno
+import re
+import subprocess
+import signal
+import tempfile
+import random
+import glob
+from dogtail.config import config
+
+
+def scratchFile(label): # pragma: no cover
+ """Uses tempfile.NamedTemporaryFile() to create a unique tempfile in
+ config.scratchDir, with a filename like:
+ dogtail-headless-<label>.<random junk>"""
+ prefix = "dogtail-headless-"
+ return tempfile.NamedTemporaryFile(prefix="%s%s." % (prefix, label),
+ dir=config.scratchDir)
+
+
+def testBinary(path): # pragma: no cover
+ if (path.startswith(os.path.sep) or
+ path.startswith(os.path.join('.', '')) or
+ path.startswith(os.path.join('..', ''))):
+ if not os.path.exists(path):
+ raise IOError(errno.ENOENT, "No such file", path)
+ if not os.access(path, os.X_OK):
+ raise IOError(errno.ENOEXEC, "Permission denied", path)
+ return True
+
+
+def get_username(): # pragma: no cover
+ return pwd.getpwuid(os.getuid())[0]
+
+
+class Subprocess(object): # pragma: no cover
+
+ def __init__(self, cmdList, environ=None):
+ testBinary(cmdList[0])
+ self.cmdList = cmdList
+ self.environ = environ
+ self._exitCode = None
+
+ def start(self):
+ if self.environ is None:
+ self.environ = os.environ
+ self.popen = subprocess.Popen(
+ self.cmdList, env=self.environ) # , stdout = subprocess.PIPE,
+ # stderr = subprocess.STDOUT, close_fds = True)
+ return self.popen.pid
+
+ def wait(self):
+ return self.popen.wait()
+
+ def stop(self):
+ # The following doesn't exist in python < 2.6, if you can believe it.
+ # self.popen.terminate()
+ os.kill(self.popen.pid, signal.SIGTERM)
+
+ @property
+ def exitCode(self):
+ if self._exitCode is None:
+ self._exitCode = self.wait()
+ return self._exitCode
+
+
+class XServer(Subprocess): # pragma: no cover
+
+ def __init__(self, server="/usr/bin/Xorg",
+ xinitrc="/etc/X11/xinit/Xclients",
+ resolution="1024x768x16"):
+ """resolution is only used with Xvfb."""
+ testBinary(server)
+ self.server = server
+ self._exitCode = None
+ self.xinit = "/usr/bin/xinit"
+ self.display = None
+ self.xinitrc = xinitrc
+ self.resolution = resolution
+
+ @staticmethod
+ def findFreeDisplay():
+ tmp = os.listdir('/tmp')
+ pattern = re.compile('\.X([0-9]+)-lock')
+ usedDisplays = []
+ for file in tmp:
+ match = re.match(pattern, file)
+ if match:
+ usedDisplays.append(int(match.groups()[0]))
+ if not usedDisplays:
+ return ':0'
+ usedDisplays.sort()
+ return ':' + str(usedDisplays[-1] + 1)
+
+ @property
+ def cmdList(self):
+ self.display = self.findFreeDisplay()
+ cmd = []
+ if self.xinit:
+ cmd.append(self.xinit)
+ if self.xinitrc:
+ cmd.append(self.xinitrc)
+ cmd.append('--')
+ cmd.append(self.server)
+ cmd.append(self.display)
+ cmd.extend(['-ac', '-noreset'])
+ if self.server.endswith('Xvfb'):
+ cmd.extend(['-screen', '0', self.resolution])
+ cmd.append('-shmem')
+ return cmd
+
+ def start(self):
+ print(' '.join(self.cmdList))
+ self.popen = subprocess.Popen(self.cmdList)
+ return self.popen.pid
+
+
+class Script(Subprocess): # pragma: no cover
+ pass
+
+
+class Session(object): # pragma: no cover
+
+ cookieName = "DOGTAIL_SESSION_COOKIE"
+
+ def __init__(self, sessionBinary, scriptCmdList=[], scriptDelay=20, logout=True):
+ testBinary(sessionBinary)
+ self.sessionBinary = sessionBinary
+ self.script = Script(scriptCmdList)
+ self.scriptDelay = scriptDelay
+ self.logout = logout
+ self.xserver = XServer()
+ self._cookie = None
+ self._environment = None
+
+ def start(self):
+ self.xinitrcFileObj = scratchFile('xinitrc')
+ self.xserver.xinitrc = self.xinitrcFileObj.name
+ self._buildXInitRC(self.xinitrcFileObj)
+ xServerPid = self.xserver.start()
+ time.sleep(self.scriptDelay)
+ self.script.environ = self.environment
+ scriptPid = self.script.start()
+ return (xServerPid, scriptPid)
+
+ @property
+ def environment(self):
+ def isSessionProcess(fileName):
+ try:
+ if os.path.realpath(path + 'exe') != ('/usr/bin/plasma-desktop'
+ if self.sessionBinary.split('/')[-1] == 'startkde'
+ else self.sessionBinary):
+ return False
+ except OSError:
+ return False
+ pid = fileName.split('/')[2]
+ if pid == 'self' or pid == str(os.getpid()):
+ return False
+ return True
+
+ def getEnvDict(fileName):
+ try:
+ envString = open(fileName, 'r').read()
+ except IOError:
+ return {}
+ envItems = envString.split('\x00')
+ envDict = {}
+ for item in envItems:
+ if not '=' in item:
+ continue
+ k, v = item.split('=', 1)
+ envDict[k] = v
+ return envDict
+
+ def isSessionEnv(envDict):
+ if not envDict:
+ return False
+ if envDict.get(self.cookieName, 'notacookie') == self.cookie:
+ return True
+ return False
+
+ for path in glob.glob('/proc/*/'):
+ if not isSessionProcess(path):
+ continue
+ envFile = path + 'environ'
+ envDict = getEnvDict(envFile)
+ if isSessionEnv(envDict):
+ # print path
+ # print envDict
+ self._environment = envDict
+ if not self._environment:
+ raise RuntimeError("Can't find our environment!")
+ return self._environment
+
+ def wait(self):
+ self.script.wait()
+ return self.xserver.wait()
+
+ def stop(self):
+ try:
+ self.script.stop()
+ except OSError:
+ pass
+ self.xserver.stop()
+
+ def attemptLogout(self):
+ logoutScript = Script('dogtail-logout',
+ environ=self.environment)
+ logoutScript.start()
+ logoutScript.wait()
+
+ @property
+ def cookie(self):
+ if not self._cookie:
+ self._cookie = "%X" % random.getrandbits(16)
+ return self._cookie
+
+ def _buildXInitRC(self, fileObj):
+ lines = [
+ "export %s=%s" % (self.cookieName, self.cookie),
+ "gsettings set org.gnome.desktop.interface toolkit-accessibility true",
+ ". /etc/X11/xinit/xinitrc-common",
+ "export %s" % self.cookieName,
+ "exec -l $SHELL -c \"$CK_XINIT_SESSION $SSH_AGENT %s\"" %
+ (self.sessionBinary),
+ ""]
+
+ fileObj.write('\n'.join(lines).strip())
+ fileObj.flush()
diff --git a/dogtail/tc.py b/dogtail/tc.py
new file mode 100755
index 00000000000..c7ef9f5bc23
--- /dev/null
+++ b/dogtail/tc.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+"""Test Case magic
+
+Author: Ed Rousseau <rousseau@redhat.com>"""
+__author__ = "Ed Rousseau <rousseau@redhat.com>"
+
+import os
+import os.path
+from config import config
+from logging import ResultsLogger, TimeStamp
+from PIL import Image, ImageChops, ImageStat
+from __builtin__ import unicode, long
+
+
+class TC(object): # pragma: no cover
+
+ """
+ The Test Case Superclass
+ """
+ logger = ResultsLogger()
+
+ def __init__(self):
+ self.encoding = config.encoding
+ # ascii + unicode. 8 bit extended char has been ripped out
+ self.supportedtypes = (
+ "ascii", "utf-8", "utf-16", "utf-16-be", "utf-16-le", "unicode-escape", "raw-unicode-escape",
+ "big5", "gb18030", "eucJP", "eucKR", "shiftJIS")
+
+ # String comparison function
+ def compare(self, label, baseline, undertest, encoding=config.encoding):
+ """
+ Compares 2 strings to see if they are the same. The user may specify
+ the encoding to which the two strings are to be normalized for the
+ comparison. Default encoding is the default system encoding.
+ Normalization to extended 8 bit charactersets is not supported.
+
+ When the origin of either baseline or undertest is a text file whose
+ encoding is something other than ASCII, it is necessary to use
+ codecs.open() instead of open(), so the file's encoding may be
+ specified.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ for string in [self.baseline, self.undertest]:
+ try:
+ string = unicode(string, 'utf-8')
+ except TypeError:
+ pass
+ self.encoding = encoding
+
+ # Normalize the encoding type for the comparaison based on
+ # self.encoding
+ if self.encoding in self.supportedtypes:
+ self.baseline = (self.baseline).encode(self.encoding)
+ self.undertest = (self.undertest).encode(self.encoding)
+ # Compare the strings
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed"}
+ else:
+ self.result = {self.label: "Failed - " + self.encoding +
+ " strings do not match. " + self.baseline + " expected: Got " + self.undertest}
+ # Pass the test result to the ResultsLogger for writing
+ TC.logger.log(self.result)
+ return self.result
+
+ else:
+ # We should probably raise an exception here
+ self.result = {
+ self.label: "ERROR - " + self.encoding + " is not a supported encoding type"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+# String Test Case subclass
+class TCString(TC): # pragma: no cover
+
+ """
+ String Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+
+# Image test case subclass
+
+
+class TCImage(TC): # pragma: no cover
+
+ """
+ Image Test Case Class.
+ """
+
+ def compare(self, label, baseline, undertest):
+ for _file in (baseline, undertest):
+ if type(_file) is not unicode and type(_file) is not str:
+ raise TypeError("Need filenames!")
+ self.label = label.strip()
+ self.baseline = baseline.strip()
+ self.undertest = undertest.strip()
+ diffName = TimeStamp().fileStamp("diff") + ".png"
+ self.diff = os.path.normpath(
+ os.path.sep.join((config.scratchDir, diffName)))
+
+ self.baseImage = Image.open(self.baseline)
+ self.testImage = Image.open(self.undertest)
+ try:
+ if self.baseImage.size != self.testImage.size:
+ self.result = {
+ self.label: "Failed - images are different sizes"}
+ raise StopIteration
+
+ self.diffImage = ImageChops.difference(self.baseImage,
+ self.testImage)
+ self.diffImage.save(self.diff)
+ result = False
+ for stat in ('stddev', 'mean', 'sum2'):
+ for item in getattr(ImageStat.Stat(self.diffImage), stat):
+ if item:
+ self.result = {self.label: "Failed - see %s" %
+ self.diff}
+ raise StopIteration
+ else:
+ result = True
+ except StopIteration:
+ result = False
+
+ if result:
+ self.result = {self.label: "Passed"}
+
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCNumber(TC): # pragma: no cover
+
+ """
+ Number Comparaison Test Case Class
+ """
+
+ def __init__(self):
+ TC.__init__(self)
+ self.supportedtypes = ("int", "long", "float", "complex", "oct", "hex")
+
+ # Compare 2 numbers by the type provided in the type arg
+ def compare(self, label, baseline, undertest, type):
+ """
+ Compares 2 numbers to see if they are the same. The user may specify
+ how to normalize mixed type comparisons via the type argument.
+ """
+ self.label = label.strip()
+ self.baseline = baseline
+ self.undertest = undertest
+ self.type = type.strip()
+
+ # If we get a valid type, convert to that type and compare
+ if self.type in self.supportedtypes:
+ # Normalize for comparison
+ if self.type == "int":
+ self.baseline = int(self.baseline)
+ self.undertest = int(self.undertest)
+ elif self.type == "long":
+ self.baseline = long(self.baseline)
+ self.undertest = long(self.undertest)
+ elif self.type == "float":
+ self.baseline = float(self.baseline)
+ self.undertest = float(self.undertest)
+ else:
+ self.baseline = complex(self.baseline)
+ self.undertest = complex(self.undertest)
+
+ # compare
+ if self.baseline == self.undertest:
+ self.result = {self.label: "Passed - numbers are the same"}
+ else:
+ self.result = {self.label: "Failed - " + str(
+ self.baseline) + " expected: Got " + str(self.undertest)}
+ TC.logger.log(self.result)
+ return self.result
+ else:
+ self.result = {
+ self.label: "Failed - " + self.type + " is not in list of supported types"}
+ TC.logger.log(self.result)
+ return self.result
+
+
+class TCBool(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, _bool):
+ """
+ If _bool is True, pass.
+ If _bool is False, fail.
+ """
+ if type(_bool) is not bool:
+ raise TypeError
+ if _bool:
+ result = {label: "Passed"}
+ else:
+ result = {label: "Failed"}
+ TC.logger.log(result)
+
+from tree import Node
+
+
+class TCNode(TC): # pragma: no cover
+
+ def __init__(self):
+ pass
+
+ def compare(self, label, baseline, undertest):
+ """
+ If baseline is None, simply check that undertest is a Node.
+ If baseline is a Node, check that it is equal to undertest.
+ """
+ if baseline is not None and not isinstance(baseline, Node):
+ raise TypeError
+
+ if not isinstance(undertest, Node):
+ result = {label: "Failed - %s is not a Node" % undertest}
+ elif baseline is None:
+ result = {label: "Passed - %s is a Node" % undertest}
+ elif isinstance(baseline, Node):
+ if baseline == undertest:
+ result = {label: "Passed - %s == %s" % (baseline, undertest)}
+ else:
+ result = {label: "Failed - %s != %s" % (baseline, undertest)}
+ TC.logger.log(result)
diff --git a/dogtail/tree.py b/dogtail/tree.py
new file mode 100755
index 00000000000..e6050bbb98b
--- /dev/null
+++ b/dogtail/tree.py
@@ -0,0 +1,1318 @@
+"""Makes some sense of the AT-SPI API
+
+The tree API handles various things for you:
+ - fixes most timing issues
+ - can automatically generate (hopefully) highly-readable logs of what the
+script is doing
+ - traps various UI malfunctions, raising exceptions for them (again,
+hopefully improving the logs)
+
+The most important class is Node. Each Node is an element of the desktop UI.
+There is a tree of nodes, starting at 'root', with applications as its
+children, with the top-level windows and dialogs as their children. The various
+widgets that make up the UI appear as descendents in this tree. All of these
+elements (root, the applications, the windows, and the widgets) are represented
+as instances of Node in a tree (provided that the program of interest is
+correctly exporting its user-interface to the accessibility system). The Node
+class is a mixin for Accessible and the various Accessible interfaces.
+
+The Action class represents an action that the accessibility layer exports as
+performable on a specific node, such as clicking on it. It's a wrapper around
+Accessibility.Action.
+
+We often want to look for a node, based on some criteria, and this is provided
+by the Predicate class.
+
+Dogtail implements a high-level searching system, for finding a node (or
+nodes) satisfying whatever criteria you are interested in. It does this with
+a 'backoff and retry' algorithm. This fixes most timing problems e.g. when a
+dialog is in the process of opening but hasn't yet done so.
+
+If a search fails, it waits 'config.searchBackoffDuration' seconds, and then
+tries again, repeatedly. After several failed attempts (determined by
+config.searchWarningThreshold) it will start sending warnings about the search
+to the debug log. If it still can't succeed after 'config.searchCutoffCount'
+attempts, it raises an exception containing details of the search. You can see
+all of this process in the debug log by setting 'config.debugSearching' to True
+
+We also automatically add a short delay after each action
+('config.defaultDelay' gives the time in seconds). We'd hoped that the search
+backoff and retry code would eliminate the need for this, but unfortunately we
+still run into timing issues. For example, Evolution (and probably most
+other apps) set things up on new dialogs and wizard pages as they appear, and
+we can run into 'setting wars' where the app resets the widgetry to defaults
+after our script has already filled out the desired values, and so we lose our
+values. So we give the app time to set the widgetry up before the rest of the
+script runs.
+
+The classes trap various UI malfunctions and raise exceptions that better
+describe what went wrong. For example, they detects attempts to click on an
+insensitive UI element and raise a specific exception for this.
+
+Unfortunately, some applications do not set up the 'sensitive' state
+correctly on their buttons (e.g. Epiphany on form buttons in a web page). The
+current workaround for this is to set config.ensureSensitivity=False, which
+disables the sensitivity testing.
+
+Authors: Zack Cerza <zcerza@redhat.com>, David Malcolm <dmalcolm@redhat.com>
+"""
+__author__ = """Zack Cerza <zcerza@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+from config import config
+if config.checkForA11y:
+ from utils import checkForA11y
+ checkForA11y()
+
+import predicate
+from time import sleep
+from utils import doDelay
+from utils import Blinker
+from utils import Lock
+import rawinput
+import path
+from __builtin__ import xrange
+
+from logging import debugLogger as logger
+
+try:
+ import pyatspi
+ import Accessibility
+except ImportError: # pragma: no cover
+ raise ImportError("Error importing the AT-SPI bindings")
+
+# We optionally import the bindings for libWnck.
+try:
+ from gi.repository import Wnck
+ gotWnck = True # pragma: no cover
+except ImportError:
+ # Skip this warning, since the functionality is almost entirely nonworking anyway.
+ # print "Warning: Dogtail could not import the Python bindings for
+ # libwnck. Window-manager manipulation will not be available."
+ gotWnck = False
+
+from gi.repository import GLib
+
+haveWarnedAboutChildrenLimit = False
+
+
+class SearchError(Exception):
+ pass
+
+
+class NotSensitiveError(Exception):
+
+ """
+ The widget is not sensitive.
+ """
+ message = "Cannot %s %s. It is not sensitive."
+
+ def __init__(self, action):
+ self.action = action
+
+ def __str__(self):
+ return self.message % (self.action.name, self.action.node.getLogString())
+
+
+class ActionNotSupported(Exception):
+
+ """
+ The widget does not support the requested action.
+ """
+ message = "Cannot do '%s' action on %s"
+
+ def __init__(self, actionName, node):
+ self.actionName = actionName
+ self.node = node
+
+ def __str__(self):
+ return self.message % (self.actionName, self.node.getLogString())
+
+
+class Action(object):
+
+ """
+ Class representing an action that can be performed on a specific node
+ """
+ # Valid types of actions we know about. Feel free to add any you see.
+ types = ('click',
+ 'press',
+ 'release',
+ 'activate',
+ 'jump',
+ 'check',
+ 'dock',
+ 'undock',
+ 'open',
+ 'menu')
+
+ def __init__(self, node, action, index):
+ self.node = node
+ self.__action = action
+ self.__index = index
+
+ @property
+ def name(self):
+ return self.__action.getName(self.__index)
+
+ @property
+ def description(self):
+ return self.__action.getDescription(self.__index)
+
+ @property
+ def keyBinding(self):
+ return self.__action.getKeyBinding(self.__index)
+
+ def __str__(self):
+ return "[action | %s | %s ]" % \
+ (self.name, self.keyBinding)
+
+ def do(self):
+ """
+ Performs the given tree.Action, with appropriate delays and logging.
+ """
+ logger.log("%s on %s" % (self.name, self.node.getLogString()))
+ if not self.node.sensitive:
+ if config.ensureSensitivity:
+ raise NotSensitiveError(self)
+ else:
+ nSE = NotSensitiveError(self)
+ logger.log("Warning: " + str(nSE))
+ if config.blinkOnActions:
+ self.node.blink()
+ result = self.__action.doAction(self.__index)
+ doDelay(config.actionDelay)
+ return result
+
+
+class Node(object):
+
+ """
+ A node in the tree of UI elements. This class is mixed in with
+ Accessibility.Accessible to both make it easier to use and to add
+ additional functionality. It also has a debugName which is set up
+ automatically when doing searches.
+ """
+
+ def __setupUserData(self):
+ try:
+ len(self.user_data)
+ except (AttributeError, TypeError):
+ self.user_data = {}
+
+ def debugName():
+ doc = "debug name assigned during search operations"
+
+ def fget(self):
+ self.__setupUserData()
+ return self.user_data.get('debugName', None)
+
+ def fset(self, debugName):
+ self.__setupUserData()
+ self.user_data['debugName'] = debugName
+
+ return property(**locals())
+ debugName = debugName()
+ #
+ # Accessible
+ #
+
+ @property
+ def dead(self):
+ """Is the node dead (defunct) ?"""
+ try:
+ if self.roleName == 'invalid':
+ return True
+ self.role
+ self.name
+ if len(self) > 0:
+ self[0]
+ except:
+ return True
+ return False
+
+ @property
+ def children(self):
+ """a list of this Accessible's children"""
+ if self.parent and self.parent.roleName == 'hyper link':
+ print(self.parent.role)
+ return []
+ children = []
+ childCount = self.childCount
+ if childCount > config.childrenLimit:
+ global haveWarnedAboutChildrenLimit
+ if not haveWarnedAboutChildrenLimit:
+ logger.log("Only returning %s children. You may change "
+ "config.childrenLimit if you wish. This message will only"
+ " be printed once." % str(config.childrenLimit))
+ haveWarnedAboutChildrenLimit = True
+ childCount = config.childrenLimit
+ for i in range(childCount):
+ # Workaround for GNOME bug #465103
+ # also solution for GNOME bug #321273
+ try:
+ child = self[i]
+ except LookupError:
+ child = None
+ if child:
+ children.append(child)
+
+ invalidChildren = childCount - len(children)
+ if invalidChildren and config.debugSearching:
+ logger.log("Skipped %s invalid children of %s" %
+ (invalidChildren, str(self)))
+ try:
+ ht = self.queryHypertext()
+ for li in range(ht.getNLinks()):
+ link = ht.getLink(li)
+ for ai in range(link.nAnchors):
+ child = link.getObject(ai)
+ child.__setupUserData()
+ child.user_data['linkAnchor'] = \
+ LinkAnchor(node=child,
+ hypertext=ht,
+ linkIndex=li,
+ anchorIndex=ai)
+ children.append(child)
+ except (NotImplementedError, AttributeError):
+ pass
+
+ return children
+
+ roleName = property(Accessibility.Accessible.getRoleName)
+
+ role = property(Accessibility.Accessible.getRole)
+
+ indexInParent = property(Accessibility.Accessible.getIndexInParent)
+
+ #
+ # Action
+ #
+
+ # Needed to be renamed from doAction due to conflicts
+ # with 'Accessibility.Accessible.doAction' in gtk3 branch
+ def doActionNamed(self, name):
+ """
+ Perform the action with the specified name. For a list of actions
+ supported by this instance, check the 'actions' property.
+ """
+ actions = self.actions
+ if name in actions:
+ return actions[name].do()
+ raise ActionNotSupported(name, self)
+
+ @property
+ def actions(self):
+ """
+ A dictionary of supported action names as keys, with Action objects as
+ values. Common action names include:
+
+ 'click' 'press' 'release' 'activate' 'jump' 'check' 'dock' 'undock'
+ 'open' 'menu'
+ """
+ actions = {}
+ try:
+ action = self.queryAction()
+ for i in range(action.nActions):
+ a = Action(self, action, i)
+ actions[action.getName(i)] = a
+ finally:
+ return actions
+
+ def combovalue():
+ doc = "The value (as a string) currently selected in the combo box."
+
+ def fget(self):
+ return self.name
+
+ def fset(self, value):
+ logger.log("Setting combobox %s to '%s'" % (self.getLogString(),
+ value))
+ self.childNamed(childName=value).doActionNamed('click')
+ doDelay()
+
+ return property(**locals())
+ combovalue = combovalue()
+ #
+ # Hypertext and Hyperlink
+ #
+
+ @property
+ def URI(self):
+ try:
+ return self.user_data['linkAnchor'].URI
+ except (KeyError, AttributeError):
+ raise NotImplementedError
+
+ #
+ # Text and EditableText
+ #
+ def text():
+ doc = """For instances with an AccessibleText interface, the text as a
+ string. This is read-only, unless the instance also has an
+ AccessibleEditableText interface. In this case, you can write values
+ to the attribute. This will get logged in the debug log, and a delay
+ will be added.
+
+ If this instance corresponds to a password entry, use the passwordText
+ property instead."""
+
+ def fget(self):
+ try:
+ return self.queryText().getText(0, -1)
+ except NotImplementedError:
+ return None
+
+ def fset(self, text):
+ try:
+ if config.debugSearching:
+ msg = "Setting text of %s to %s"
+ # Let's not get too crazy if 'text' is really large...
+ # FIXME: Sometimes the next line screws up Unicode strings.
+ if len(text) > 140:
+ txt = text[:134] + " [...]"
+ else:
+ txt = text
+ logger.log(msg % (self.getLogString(), "'%s'" % txt))
+ self.queryEditableText().setTextContents(text)
+ except NotImplementedError:
+ raise AttributeError("can't set attribute")
+
+ return property(**locals())
+ text = text()
+
+ def caretOffset():
+
+ def fget(self):
+ """For instances with an AccessibleText interface, the caret
+ offset as an integer."""
+ return self.queryText().caretOffset
+
+ def fset(self, offset):
+ return self.queryText().setCaretOffset(offset)
+
+ return property(**locals())
+ caretOffset = caretOffset()
+
+ #
+ # Component
+ #
+
+ @property
+ def position(self):
+ """A tuple containing the position of the Accessible: (x, y)"""
+ return self.queryComponent().getPosition(pyatspi.DESKTOP_COORDS)
+
+ @property
+ def size(self):
+ """A tuple containing the size of the Accessible: (w, h)"""
+ return self.queryComponent().getSize()
+
+ @property
+ def extents(self):
+ """A tuple containing the location and size of the Accessible:
+ (x, y, w, h)"""
+ try:
+ ex = self.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ return (ex.x, ex.y, ex.width, ex.height)
+ except NotImplementedError:
+ return None
+
+ def contains(self, x, y):
+ try:
+ return self.queryComponent().contains(x, y, pyatspi.DESKTOP_COORDS)
+ except NotImplementedError:
+ return False
+
+ def getChildAtPoint(self, x, y):
+ node = self
+ while True:
+ try:
+ child = node.queryComponent().getAccessibleAtPoint(x, y,
+ pyatspi.DESKTOP_COORDS)
+ if child and child.contains(x, y):
+ node = child
+ else:
+ break
+ except NotImplementedError:
+ break
+ if node and node.contains(x, y):
+ return node
+ else:
+ return None
+
+ def grabFocus(self):
+ "Attempts to set the keyboard focus to this Accessible."
+ return self.queryComponent().grabFocus()
+
+ # def blink(self, count=2):
+ #"""
+ # Blink, baby!
+ #"""
+ # if not self.extents: return False
+ # else:
+ #(x, y, w, h) = self.extents
+ #from utils import Blinker
+ #blinkData = Blinker(x, y, w, h, count)
+ # return True
+
+ def click(self, button=1):
+ """
+ Generates a raw mouse click event, using the specified button.
+ - 1 is left,
+ - 2 is middle,
+ - 3 is right.
+ """
+ logger.log("Clicking on %s" % self.getLogString())
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.click(clickX, clickY, button)
+
+ def doubleClick(self, button=1):
+ """
+ Generates a raw mouse double-click event, using the specified button.
+ """
+ clickX = self.position[0] + self.size[0] / 2
+ clickY = self.position[1] + self.size[1] / 2
+ if config.debugSearching:
+ logger.log("raw click on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(clickX), str(clickY)))
+ rawinput.doubleClick(clickX, clickY, button)
+
+ def point(self, mouseDelay=None):
+ """
+ Move mouse cursor to the center of the widget.
+ """
+ pointX = self.position[0] + self.size[0] / 2
+ pointY = self.position[1] + self.size[1] / 2
+ logger.log("Pointing on %s %s at (%s,%s)" %
+ (self.name, self.getLogString(), str(pointX), str(pointY)))
+ rawinput.registry.generateMouseEvent(pointX, pointY, 'abs')
+ if mouseDelay:
+ doDelay(mouseDelay)
+ else:
+ doDelay()
+
+ #
+ # RelationSet
+ #
+ @property
+ def labeler(self):
+ """'labeller' (read-only list of Node instances):
+ The node(s) that is/are a label for this node. Generated from
+ 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABELLED_BY:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labeller = labeler
+
+ @property
+ def labelee(self):
+ """'labellee' (read-only list of Node instances):
+ The node(s) that this node is a label for. Generated from 'relations'.
+ """
+ relationSet = self.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
+ if relation.getNTargets() == 1:
+ return relation.getTarget(0)
+ targets = []
+ for i in range(relation.getNTargets()):
+ targets.append(relation.getTarget(i))
+ return targets
+ labellee = labelee
+
+ #
+ # StateSet
+ #
+ @property
+ def sensitive(self):
+ """Is the Accessible sensitive (i.e. not greyed out)?"""
+ return self.getState().contains(pyatspi.STATE_SENSITIVE)
+
+ @property
+ def showing(self):
+ return self.getState().contains(pyatspi.STATE_SHOWING)
+
+ @property
+ def focusable(self):
+ """Is the Accessible capable of having keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSABLE)
+
+ @property
+ def focused(self):
+ """Does the Accessible have keyboard focus?"""
+ return self.getState().contains(pyatspi.STATE_FOCUSED)
+
+ @property
+ def checked(self):
+ """Is the Accessible a checked checkbox?"""
+ return self.getState().contains(pyatspi.STATE_CHECKED)
+
+ @property
+ def isChecked(self):
+ """Is the Accessible a checked checkbox? Compatibility property, same as Node.checked."""
+ return self.checked
+
+ #
+ # Selection
+ #
+
+ def selectAll(self):
+ """Selects all children."""
+ result = self.querySelection().selectAll()
+ doDelay()
+ return result
+
+ def deselectAll(self):
+ """Deselects all selected children."""
+ result = self.querySelection().clearSelection()
+ doDelay()
+ return result
+
+ def select(self):
+ """Selects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().selectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ def deselect(self):
+ """Deselects the Accessible."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ result = parent.querySelection().deselectChild(self.indexInParent)
+ doDelay()
+ return result
+
+ @property
+ def isSelected(self):
+ """Is the Accessible selected? Compatibility property, same as Node.selected."""
+ try:
+ parent = self.parent
+ except AttributeError:
+ raise NotImplementedError
+ return parent.querySelection().isChildSelected(self.indexInParent)
+
+ @property
+ def selected(self):
+ """Is the Accessible selected?"""
+ return self.isSelected
+
+ @property
+ def selectedChildren(self):
+ """Returns a list of children that are selected."""
+ # TODO: hideChildren for Hyperlinks?
+ selection = self.querySelection()
+ selectedChildren = []
+ for i in xrange(selection.nSelectedChildren):
+ selectedChildren.append(selection.getSelectedChild(i))
+
+ #
+ # Value
+ #
+
+ def value():
+ doc = "The value contained by the AccessibleValue interface."
+
+ def fget(self):
+ try:
+ return self.queryValue().currentValue
+ except NotImplementedError:
+ pass
+
+ def fset(self, value):
+ self.queryValue().currentValue = value
+
+ return property(**locals())
+ value = value()
+
+ @property
+ def minValue(self):
+ """The minimum value of self.value"""
+ try:
+ return self.queryValue().minimumValue
+ except NotImplementedError:
+ pass
+
+ @property
+ def minValueIncrement(self):
+ """The minimum value increment of self.value"""
+ try:
+ return self.queryValue().minimumIncrement
+ except NotImplementedError:
+ pass
+
+ @property
+ def maxValue(self):
+ """The maximum value of self.value"""
+ try:
+ return self.queryValue().maximumValue
+ except NotImplementedError:
+ pass
+
+ def typeText(self, string):
+ """
+ Type the given text into the node, with appropriate delays and
+ logging.
+ """
+ logger.log("Typing text into %s: '%s'" % (self.getLogString(), string))
+
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ rawinput.typeText(string)
+ else:
+ logger.log("Node is not focusable; falling back to inserting text")
+ et = self.queryEditableText()
+ et.insertText(self.caretOffset, string, len(string))
+ self.caretOffset += len(string)
+ doDelay()
+
+ def keyCombo(self, comboString):
+ if config.debugSearching:
+ logger.log("Pressing keys '%s' into %s" %
+ (comboString, self.getLogString()))
+ if self.focusable:
+ if not self.focused:
+ try:
+ self.grabFocus()
+ except Exception:
+ logger.log("Node is focusable but I can't grabFocus!")
+ else:
+ logger.log("Node is not focusable; trying key combo anyway")
+ rawinput.keyCombo(comboString)
+
+ def getLogString(self):
+ """
+ Get a string describing this node for the logs,
+ respecting the config.absoluteNodePaths boolean.
+ """
+ if config.absoluteNodePaths:
+ return self.getAbsoluteSearchPath()
+ else:
+ return str(self)
+
+ def satisfies(self, pred):
+ """
+ Does this node satisfy the given predicate?
+ """
+ # the logic is handled by the predicate:
+ assert isinstance(pred, predicate.Predicate)
+ return pred.satisfiedByNode(self)
+
+ def dump(self, type='plain', fileName=None):
+ import dump
+ dumper = getattr(dump, type)
+ dumper(self, fileName)
+
+ def getAbsoluteSearchPath(self):
+ """
+ FIXME: this needs rewriting...
+ Generate a SearchPath instance giving the 'best'
+ way to find the Accessible wrapped by this node again, starting
+ at the root and applying each search in turn.
+
+ This is somewhat analagous to an absolute path in a filesystem,
+ except that some of searches may be recursive, rather than just
+ searching direct children.
+
+ Used by the recording framework for identifying nodes in a
+ persistent way, independent of the style of script being
+ written.
+
+ FIXME: try to ensure uniqueness
+ FIXME: need some heuristics to get 'good' searches, whatever
+ that means
+ """
+ if config.debugSearchPaths:
+ logger.log("getAbsoluteSearchPath(%s)" % self)
+
+ if self.roleName == 'application':
+ result = path.SearchPath()
+ result.append(predicate.IsAnApplicationNamed(self.name), False)
+ return result
+ else:
+ if self.parent:
+ (ancestor, pred, isRecursive) = self.getRelativeSearch()
+ if config.debugSearchPaths:
+ logger.log("got ancestor: %s" % ancestor)
+
+ ancestorPath = ancestor.getAbsoluteSearchPath()
+ ancestorPath.append(pred, isRecursive)
+ return ancestorPath
+ else:
+ # This should be the root node:
+ return path.SearchPath()
+
+ def getRelativeSearch(self):
+ """
+ Get a (ancestorNode, predicate, isRecursive) triple that identifies the
+ best way to find this Node uniquely.
+ FIXME: or None if no such search exists?
+ FIXME: may need to make this more robust
+ FIXME: should this be private?
+ """
+ if config.debugSearchPaths:
+ logger.log("getRelativeSearchPath(%s)" % self)
+
+ assert self
+ assert self.parent
+
+ isRecursive = False
+ ancestor = self.parent
+
+ # iterate up ancestors until you reach an identifiable one,
+ # setting the search to be isRecursive if need be:
+ while not self.__nodeIsIdentifiable(ancestor):
+ ancestor = ancestor.parent
+ isRecursive = True
+
+ # Pick the most appropriate predicate for finding this node:
+ if self.labellee:
+ if self.labellee.name:
+ return (ancestor, predicate.IsLabelledAs(self.labellee.name), isRecursive)
+
+ if self.roleName == 'menu':
+ return (ancestor, predicate.IsAMenuNamed(self.name), isRecursive)
+ elif self.roleName == 'menu item' or self.roleName == 'check menu item':
+ return (ancestor, predicate.IsAMenuItemNamed(self.name), isRecursive)
+ elif self.roleName == 'text':
+ return (ancestor, predicate.IsATextEntryNamed(self.name), isRecursive)
+ elif self.roleName == 'push button':
+ return (ancestor, predicate.IsAButtonNamed(self.name), isRecursive)
+ elif self.roleName == 'frame':
+ return (ancestor, predicate.IsAWindowNamed(self.name), isRecursive)
+ elif self.roleName == 'dialog':
+ return (ancestor, predicate.IsADialogNamed(self.name), isRecursive)
+ else:
+ pred = predicate.GenericPredicate(
+ name=self.name, roleName=self.roleName)
+ return (ancestor, pred, isRecursive)
+
+ def __nodeIsIdentifiable(self, ancestor):
+ if ancestor.labellee:
+ return True
+ elif ancestor.name:
+ return True
+ elif not ancestor.parent:
+ return True
+ else:
+ return False
+
+ def _fastFindChild(self, pred, recursive=True):
+ """
+ Searches for an Accessible using methods from pyatspi.utils
+ """
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None:
+ if pred(child):
+ return child
+ else:
+ return pyatspi.utils.findDescendant(self, pred)
+
+ def findChild(self, pred, recursive=True, debugName=None,
+ retry=True, requireResult=True):
+ """
+ Search for a node satisyfing the predicate, returning a Node.
+
+ If retry is True (the default), it makes multiple attempts,
+ backing off and retrying on failure, and eventually raises a
+ descriptive exception if the search fails.
+
+ If retry is False, it gives up after one attempt.
+
+ If requireResult is True (the default), an exception is raised after all
+ attempts have failed. If it is false, the function simply returns None.
+ """
+ def describeSearch(parent, pred, recursive, debugName):
+ """
+ Internal helper function
+ """
+ if recursive:
+ noun = "descendent"
+ else:
+ noun = "child"
+ if debugName is None:
+ debugName = pred.describeSearchResult()
+ return "%s of %s: %s" % (noun, parent.getLogString(), debugName)
+
+ assert isinstance(pred, predicate.Predicate)
+ numAttempts = 0
+ while numAttempts < config.searchCutoffCount:
+ if numAttempts >= config.searchWarningThreshold or config.debugSearching:
+ logger.log("searching for %s (attempt %i)" %
+ (describeSearch(self, pred, recursive, debugName), numAttempts))
+
+ result = self._fastFindChild(pred.satisfiedByNode, recursive)
+ if result:
+ assert isinstance(result, Node)
+ if debugName:
+ result.debugName = debugName
+ else:
+ result.debugName = pred.describeSearchResult()
+ return result
+ else:
+ if not retry:
+ break
+ numAttempts += 1
+ if config.debugSearching or config.debugSleep:
+ logger.log("sleeping for %f" %
+ config.searchBackoffDuration)
+ sleep(config.searchBackoffDuration)
+ if requireResult:
+ raise SearchError(describeSearch(self, pred, recursive, debugName))
+
+ # The canonical "search for multiple" method:
+ def findChildren(self, pred, recursive=True, isLambda=False):
+ """
+ Find all children/descendents satisfying the predicate.
+ """
+ if isLambda is True:
+ nodes = self.findChildren(predicate.GenericPredicate(), recursive=recursive)
+ result = []
+ for node in nodes:
+ try:
+ if pred(node):
+ result.append(node)
+ except:
+ pass
+ return result
+ if isinstance(pred, predicate.Predicate):
+ pred = pred.satisfiedByNode
+ if not recursive:
+ cIter = iter(self)
+ result = []
+ while True:
+ try:
+ child = cIter.next()
+ except StopIteration:
+ break
+ if child is not None and pred(child):
+ result.append(child)
+ return result
+ else:
+ descendants = []
+ while True:
+ try:
+ descendants = pyatspi.utils.findAllDescendants(self, pred)
+ break
+ except (GLib.GError, TypeError):
+ continue
+ return descendants
+
+ # The canonical "search above this node" method:
+ def findAncestor(self, pred):
+ """
+ Search up the ancestry of this node, returning the first Node
+ satisfying the predicate, or None.
+ """
+ assert isinstance(pred, predicate.Predicate)
+ candidate = self.parent
+ while candidate is not None:
+ if candidate.satisfies(pred):
+ return candidate
+ else:
+ candidate = candidate.parent
+ # Not found:
+ return None
+
+ # Various wrapper/helper search methods:
+ def child(self, name='', roleName='', description='', label='', recursive=True, retry=True, debugName=None):
+ """
+ Finds a child satisying the given criteria.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.GenericPredicate(name=name, roleName=roleName, description=description, label=label), recursive=recursive, retry=retry, debugName=debugName)
+
+ def isChild(self, name='', roleName='', description='', label='', recursive=True, retry=False, debugName=None):
+ """
+ Determines whether a child satisying the given criteria exists.
+
+ This is implemented using findChild, but will not automatically retry
+ if no such child is found. To make the function retry multiple times set retry to True.
+ Returns a boolean value depending on whether the child was eventually found. Similar to
+ 'child', yet it catches SearchError exception to provide for False results, will raise
+ any other exceptions. It also logs the search.
+ """
+ found = True
+ try:
+ self.findChild(
+ predicate.GenericPredicate(
+ name=name, roleName=roleName, description=description, label=label),
+ recursive=recursive, retry=retry, debugName=debugName)
+ except SearchError:
+ found = False
+ return found
+
+ def menu(self, menuName, recursive=True):
+ """
+ Search below this node for a menu with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuNamed(menuName=menuName), recursive)
+
+ def menuItem(self, menuItemName, recursive=True):
+ """
+ Search below this node for a menu item with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAMenuItemNamed(menuItemName=menuItemName), recursive)
+
+ def textentry(self, textEntryName, recursive=True):
+ """
+ Search below this node for a text entry with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATextEntryNamed(textEntryName=textEntryName), recursive)
+
+ def button(self, buttonName, recursive=True):
+ """
+ Search below this node for a button with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsAButtonNamed(buttonName=buttonName), recursive)
+
+ def childLabelled(self, labelText, recursive=True):
+ """
+ Search below this node for a child labelled with the given text.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsLabelledAs(labelText), recursive)
+
+ def childNamed(self, childName, recursive=True):
+ """
+ Search below this node for a child with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsNamed(childName), recursive)
+
+ def tab(self, tabName, recursive=True):
+ """
+ Search below this node for a tab with the given name.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return self.findChild(predicate.IsATabNamed(tabName=tabName), recursive)
+
+ def getUserVisibleStrings(self):
+ """
+ Get all user-visible strings in this node and its descendents.
+
+ (Could be implemented as an attribute)
+ """
+ result = []
+ if self.name:
+ result.append(self.name)
+ if self.description:
+ result.append(self.description)
+ try:
+ children = self.children
+ except Exception:
+ return result
+ for child in children:
+ result.extend(child.getUserVisibleStrings())
+ return result
+
+ def blink(self):
+ """
+ Blink, baby!
+ """
+ if not self.extents:
+ return False
+ else:
+ (x, y, w, h) = self.extents
+ Blinker(x, y, w, h)
+ return True
+
+
+class LinkAnchor(object):
+
+ """
+ Class storing info about an anchor within an Accessibility.Hyperlink, which
+ is in turn stored within an Accessibility.Hypertext.
+ """
+
+ def __init__(self, node, hypertext, linkIndex, anchorIndex):
+ self.node = node
+ self.hypertext = hypertext
+ self.linkIndex = linkIndex
+ self.anchorIndex = anchorIndex
+
+ @property
+ def link(self):
+ return self.hypertext.getLink(self.linkIndex)
+
+ @property
+ def URI(self):
+ return self.link.getURI(self.anchorIndex)
+
+
+class Root (Node):
+
+ """
+ FIXME:
+ """
+
+ def applications(self):
+ """
+ Get all applications.
+ """
+ return root.findChildren(predicate.GenericPredicate(
+ roleName="application"), recursive=False)
+
+ def application(self, appName, retry=True):
+ """
+ Gets an application by name, returning an Application instance
+ or raising an exception.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+ """
+ return root.findChild(predicate.IsAnApplicationNamed(appName), recursive=False, retry=retry)
+
+
+class Application (Node):
+
+ def dialog(self, dialogName, recursive=False):
+ """
+ Search below this node for a dialog with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: should this method activate the dialog?
+ """
+ return self.findChild(predicate.IsADialogNamed(dialogName=dialogName), recursive)
+
+ def window(self, windowName, recursive=False):
+ """
+ Search below this node for a window with the given name,
+ returning a Window instance.
+
+ This is implemented using findChild, and hence will automatically retry
+ if no such child is found, and will eventually raise an exception. It
+ also logs the search.
+
+ FIXME: this bit isn't true:
+ The window will be automatically activated (raised and focused
+ by the window manager) if wnck bindings are available.
+ """
+ result = self.findChild(
+ predicate.IsAWindowNamed(windowName=windowName), recursive)
+ # FIXME: activate the WnckWindow ?
+ # if gotWnck:
+ # result.activate()
+ return result
+
+ def getWnckApplication(self): # pragma: no cover
+ """
+ Get the wnck.Application instance for this application, or None
+
+ Currently implemented via a hack: requires the app to have a
+ window, and looks up the application of that window
+
+ wnck.Application can give you the pid, the icon, etc
+
+ FIXME: untested
+ """
+ window = self.child(roleName='frame')
+ if window:
+ wnckWindow = window.getWnckWindow()
+ return wnckWindow.get_application()
+
+
+class Window (Node):
+
+ def getWnckWindow(self): # pragma: no cover
+ """
+ Get the wnck.Window instance for this window, or None
+ """
+ # FIXME: this probably needs rewriting:
+ screen = Wnck.screen_get_default()
+
+ # You have to force an update before any of the wnck methods
+ # do anything:
+ screen.force_update()
+
+ for wnckWindow in screen.get_windows():
+ # FIXME: a dubious hack: search by window title:
+ if wnckWindow.get_name() == self.name:
+ return wnckWindow
+
+ def activate(self): # pragma: no cover
+ """
+ Activates the wnck.Window associated with this Window.
+
+ FIXME: doesn't yet work
+ """
+ wnckWindow = self.getWnckWindow()
+ # Activate it with a timestamp of 0; this may confuse
+ # alt-tabbing through windows etc:
+ # FIXME: is there a better way of getting a timestamp?
+ # gdk_x11_get_server_time (), with a dummy window
+ wnckWindow.activate(0)
+
+
+class Wizard (Window):
+
+ """
+ Note that the buttons of a GnomeDruid were not accessible until
+ recent versions of libgnomeui. This is
+ http://bugzilla.gnome.org/show_bug.cgi?id=157936
+ and is fixed in gnome-2.10 and gnome-2.12 (in CVS libgnomeui);
+ there's a patch attached to that bug.
+
+ This bug is known to affect FC3; fixed in FC5
+ """
+
+ def __init__(self, node, debugName=None):
+ Node.__init__(self, node)
+ if debugName:
+ self.debugName = debugName
+ logger.log("%s is on '%s' page" % (self, self.getPageTitle()))
+
+ def currentPage(self):
+ """
+ Get the current page of this wizard
+
+ FIXME: this is currently a hack, supporting only GnomeDruid
+ """
+ pageHolder = self.child(roleName='panel')
+ for child in pageHolder.children:
+ # current child has SHOWING state set, we hope:
+ # print child
+ # print child.showing
+ if child.showing:
+ return child
+ raise "Unable to determine current page of %s" % self
+
+ def getPageTitle(self):
+ """
+ Get the string title of the current page of this wizard
+
+ FIXME: this is currently a total hack, supporting only GnomeDruid
+ """
+ currentPage = self.currentPage()
+ return currentPage.child(roleName='panel').child(roleName='panel').child(roleName='label', recursive=False).text
+
+ def clickForward(self):
+ """
+ Click on the 'Forward' button to advance to next page of wizard.
+
+ It will log the title of the new page that is reached.
+
+ FIXME: what if it's Next rather than Forward ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Forward")
+ fwd.click()
+
+ # Log the new wizard page; it's helpful when debugging scripts
+ logger.log("%s is now on '%s' page" % (self, self.getPageTitle()))
+ # FIXME disabled for now (can't get valid page titles)
+
+ def clickApply(self):
+ """
+ Click on the 'Apply' button to advance to next page of wizard.
+ FIXME: what if it's Finish rather than Apply ???
+
+ This will only work if your libgnomeui has accessible buttons;
+ see above.
+ """
+ fwd = self.child("Apply")
+ fwd.click()
+
+ # FIXME: debug logging?
+
+Accessibility.Accessible.__bases__ = (
+ Application, Root, Node,) + Accessibility.Accessible.__bases__
+
+try:
+ root = pyatspi.Registry.getDesktop(0)
+ root.debugName = 'root'
+except Exception: # pragma: no cover
+ # Warn if AT-SPI's desktop object doesn't show up.
+ logger.log(
+ "Error: AT-SPI's desktop is not visible. Do you have accessibility enabled?")
+
+# Check that there are applications running. Warn if none are.
+children = root.children
+if not children: # pragma: no cover
+ logger.log(
+ "Warning: AT-SPI's desktop is visible but it has no children. Are you running any AT-SPI-aware applications?")
+del children
+
+import os
+# sniff also imports from tree and we don't want to run this code from
+# sniff itself
+if not os.path.exists('/tmp/sniff_running.lock'):
+ if not os.path.exists('/tmp/sniff_refresh.lock'): # may have already been locked by dogtail.procedural
+ # tell sniff not to use auto-refresh while script using this module is
+ # running
+ sniff_lock = Lock(lockname='sniff_refresh.lock', randomize=False)
+ try:
+ sniff_lock.lock()
+ except OSError: # pragma: no cover
+ pass # lock was already present from other script instance or leftover from killed instance
+ # lock should unlock automatically on script exit.
+
+# Convenient place to set some debug variables:
+#config.debugSearching = True
+#config.absoluteNodePaths = True
+#config.logDebugToFile = False
diff --git a/dogtail/tree.pyc b/dogtail/tree.pyc
new file mode 100644
index 00000000000..2851c7bdd6b
--- /dev/null
+++ b/dogtail/tree.pyc
Binary files differ
diff --git a/dogtail/utils.py b/dogtail/utils.py
new file mode 100755
index 00000000000..be3c78adec2
--- /dev/null
+++ b/dogtail/utils.py
@@ -0,0 +1,395 @@
+# -*- coding: utf-8 -*-
+"""
+Various utilities
+
+Authors: Ed Rousseau <rousseau@redhat.com>, Zack Cerza <zcerza@redhat.com, David Malcolm <dmalcolm@redhat.com>
+"""
+
+__author__ = """Ed Rousseau <rousseau@redhat.com>,
+Zack Cerza <zcerza@redhat.com,
+David Malcolm <dmalcolm@redhat.com>
+"""
+
+import os
+import sys
+import subprocess
+import cairo
+import predicate
+import errno
+import shlex
+
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from config import config
+from time import sleep
+from logging import debugLogger as logger
+from logging import TimeStamp
+from __builtin__ import file
+
+
+def screenshot(file='screenshot.png', timeStamp=True):
+ """
+ This function wraps the ImageMagick import command to take a screenshot.
+
+ The file argument may be specified as 'foo', 'foo.png', or using any other
+ extension that ImageMagick supports. PNG is the default.
+
+ By default, screenshot filenames are in the format of foo_YYYYMMDD-hhmmss.png .
+ The timeStamp argument may be set to False to name the file foo.png.
+ """
+ if not isinstance(timeStamp, bool):
+ raise TypeError("timeStampt must be True or False")
+ # config is supposed to create this for us. If it's not there, bail.
+ assert os.path.isdir(config.scratchDir)
+
+ baseName = ''.join(file.split('.')[0:-1])
+ fileExt = file.split('.')[-1].lower()
+ if not baseName:
+ baseName = file
+ fileExt = 'png'
+
+ if timeStamp:
+ ts = TimeStamp()
+ newFile = ts.fileStamp(baseName) + '.' + fileExt
+ path = config.scratchDir + newFile
+ else:
+ newFile = baseName + '.' + fileExt
+ path = config.scratchDir + newFile
+
+ from gi.repository import Gdk
+ from gi.repository import GLib
+ from gi.repository import GdkPixbuf
+ rootWindow = Gdk.get_default_root_window()
+ geometry = rootWindow.get_geometry()
+ pixbuf = GdkPixbuf.Pixbuf(colorspace=GdkPixbuf.Colorspace.RGB,
+ has_alpha=False,
+ bits_per_sample=8,
+ width=geometry[2],
+ height=geometry[3])
+
+ pixbuf = Gdk.pixbuf_get_from_window(rootWindow, 0, 0,
+ geometry[2], geometry[3])
+ # GdkPixbuf.Pixbuf.save() needs 'jpeg' and not 'jpg'
+ if fileExt == 'jpg':
+ fileExt = 'jpeg'
+ try:
+ pixbuf.savev(path, fileExt, [], [])
+ except GLib.GError:
+ raise ValueError("Failed to save screenshot in %s format" % fileExt)
+ assert os.path.exists(path)
+ logger.log("Screenshot taken: " + path)
+ return path
+
+
+def run(string, timeout=config.runTimeout, interval=config.runInterval, desktop=None, dumb=False, appName=''):
+ """
+ Runs an application. [For simple command execution such as 'rm *', use os.popen() or os.system()]
+ If dumb is omitted or is False, polls at interval seconds until the application is finished starting, or until timeout is reached.
+ If dumb is True, returns when timeout is reached.
+ """
+ if not desktop:
+ from tree import root as desktop
+ args = shlex.split(string)
+ os.environ['GTK_MODULES'] = 'gail:atk-bridge'
+ pid = subprocess.Popen(args, env=os.environ).pid
+
+ if not appName:
+ appName = args[0]
+
+ if dumb:
+ # We're starting a non-AT-SPI-aware application. Disable startup
+ # detection.
+ doDelay(timeout)
+ else:
+ # Startup detection code
+ # The timing here is not totally precise, but it's good enough for now.
+ time = 0
+ while time < timeout:
+ time = time + interval
+ try:
+ for child in desktop.children[::-1]:
+ if child.name == appName:
+ for grandchild in child.children:
+ if grandchild.roleName == 'frame':
+ from procedural import focus
+ focus.application.node = child
+ doDelay(interval)
+ return pid
+ except AttributeError: # pragma: no cover
+ pass
+ doDelay(interval)
+ return pid
+
+
+def doDelay(delay=None):
+ """
+ Utility function to insert a delay (with logging and a configurable
+ default delay)
+ """
+ if delay is None:
+ delay = config.defaultDelay
+ if config.debugSleep:
+ logger.log("sleeping for %f" % delay)
+ sleep(delay)
+
+
+class Highlight (Gtk.Window): # pragma: no cover
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ super(Highlight, self).__init__()
+ self.set_decorated(False)
+ self.set_has_resize_grip(False)
+ self.set_default_size(w, h)
+ self.screen = self.get_screen()
+ self.visual = self.screen.get_rgba_visual()
+ if self.visual is not None and self.screen.is_composited():
+ self.set_visual(self.visual)
+ self.set_app_paintable(True)
+ self.connect("draw", self.area_draw)
+ self.show_all()
+ self.move(x, y)
+
+ def area_draw(self, widget, cr): # pragma: no cover
+ cr.set_source_rgba(.0, .0, .0, 0.0)
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
+ cr.set_source_rgb(0.9, 0.1, 0.1)
+ cr.set_line_width(6)
+ cr.rectangle(0, 0, self.get_size()[0], self.get_size()[1])
+ cr.stroke()
+
+
+class Blinker(object): # pragma: no cover
+ INTERVAL_MS = 1000
+ main_loop = GLib.MainLoop()
+
+ def __init__(self, x, y, w, h): # pragma: no cover
+ self.highlight_window = Highlight(x, y, w, h)
+ if self.highlight_window.screen.is_composited() is not False:
+ self.timeout_handler_id = GLib.timeout_add(
+ Blinker.INTERVAL_MS, self.destroyHighlight)
+ self.main_loop.run()
+ else:
+ self.highlight_window.destroy()
+
+ def destroyHighlight(self): # pragma: no cover
+ self.highlight_window.destroy()
+ self.main_loop.quit()
+ return False
+
+
+class Lock(object):
+
+ """
+ A mutex implementation that uses atomicity of the mkdir operation in UNIX-like
+ systems. This can be used by scripts to provide for mutual exlusion, either in single
+ scripts using threads etc. or i.e. to handle sitations of possible collisions among
+ multiple running scripts. You can choose to make randomized single-script wise locks
+ or a more general locks if you do not choose to randomize the lockdir name
+ """
+
+ def __init__(self, location='/tmp', lockname='dogtail_lockdir_', randomize=True):
+ """
+ You can change the default lockdir location or name. Setting randomize to
+ False will result in no random string being appened to the lockdir name.
+ """
+ self.lockdir = os.path.join(os.path.normpath(location), lockname)
+ if randomize:
+ self.lockdir = "%s%s" % (self.lockdir, self.__getPostfix())
+
+ def lock(self):
+ """
+ Creates a lockdir based on the settings on Lock() instance creation.
+ Raises OSError exception of the lock is already present. Should be
+ atomic on POSIX compliant systems.
+ """
+ locked_msg = 'Dogtail lock: Already locked with the same lock'
+ if not os.path.exists(self.lockdir):
+ try:
+ os.mkdir(self.lockdir)
+ return self.lockdir
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(self.lockdir):
+ raise OSError(locked_msg)
+ else:
+ raise OSError(locked_msg)
+
+ def unlock(self):
+ """
+ Removes a lock. Will raise OSError exception if the lock was not present.
+ Should be atomic on POSIX compliant systems.
+ """
+ import os # have to import here for situations when executed from __del__
+ if os.path.exists(self.lockdir):
+ try:
+ os.rmdir(self.lockdir)
+ except OSError as e:
+ if e.erron == errno.EEXIST:
+ raise OSError('Dogtail unlock: lockdir removed elsewhere!')
+ else:
+ raise OSError('Dogtail unlock: not locked')
+
+ def __del__(self):
+ """
+ Makes sure lock is removed when the process ends. Although not when killed indeed.
+ """
+ self.unlock()
+
+ def __getPostfix(self):
+ import random
+ import string
+ return ''.join(random.choice(string.letters + string.digits) for x in range(5))
+
+
+a11yDConfKey = 'org.gnome.desktop.interface'
+
+
+def isA11yEnabled():
+ """
+ Checks if accessibility is enabled via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(a11yDConfKey)
+ dconfEnabled = InterfaceSettings.get_boolean('toolkit-accessibility')
+ if os.environ.get('GTK_MODULES', '').find('gail:atk-bridge') == -1:
+ envEnabled = False
+ else:
+ envEnabled = True # pragma: no cover
+ return (dconfEnabled or envEnabled)
+
+
+def bailBecauseA11yIsDisabled():
+ if sys.argv[0].endswith("pydoc"):
+ return # pragma: no cover
+ try:
+ if file("/proc/%s/cmdline" % os.getpid()).read().find('epydoc') != -1:
+ return # pragma: no cover
+ except: # pragma: no cover
+ pass # pragma: no cover
+ logger.log("Dogtail requires that Assistive Technology support be enabled."
+ "\nYou can enable accessibility with sniff or by running:\n"
+ "'gsettings set org.gnome.desktop.interface toolkit-accessibility true'\nAborting...")
+ sys.exit(1)
+
+
+def enableA11y(enable=True):
+ """
+ Enables accessibility via DConf.
+ """
+ from gi.repository.Gio import Settings
+ InterfaceSettings = Settings(schema=a11yDConfKey)
+ InterfaceSettings.set_boolean('toolkit-accessibility', enable)
+
+
+def checkForA11y():
+ """
+ Checks if accessibility is enabled, and halts execution if it is not.
+ """
+ if not isA11yEnabled(): # pragma: no cover
+ bailBecauseA11yIsDisabled()
+
+
+def checkForA11yInteractively(): # pragma: no cover
+ """
+ Checks if accessibility is enabled, and presents a dialog prompting the
+ user if it should be enabled if it is not already, then halts execution.
+ """
+ if isA11yEnabled():
+ return
+ from gi.repository import Gtk
+ dialog = Gtk.Dialog('Enable Assistive Technology Support?',
+ None,
+ Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ (Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE,
+ "_Enable", Gtk.ResponseType.ACCEPT))
+ question = """Dogtail requires that Assistive Technology Support be enabled for it to function. Would you like to enable Assistive Technology support now?
+
+Note that you will have to log out for the change to fully take effect.
+ """.strip()
+ dialog.set_default_response(Gtk.ResponseType.ACCEPT)
+ questionLabel = Gtk.Label(label=question)
+ questionLabel.set_line_wrap(True)
+ dialog.vbox.pack_start(questionLabel, True, True, 0)
+ dialog.show_all()
+ result = dialog.run()
+ if result == Gtk.ResponseType.ACCEPT:
+ logger.log("Enabling accessibility...")
+ enableA11y()
+ elif result == Gtk.ResponseType.CLOSE:
+ bailBecauseA11yIsDisabled()
+ dialog.destroy()
+
+
+class GnomeShell(object): # pragma: no cover
+
+ """
+ Utility class to help working with certain atributes of gnome-shell.
+ Currently that means handling the Application menu available for apps
+ on the top gnome-shell panel. Searching for the menu and its items is
+ somewhat tricky due to fuzzy a11y tree of gnome-shell, mainly since the
+ actual menu is not present as child to the menu-spawning button. Also,
+ the menus get constructed/destroyed on the fly with application focus
+ changes. Thus current application name as displayed plus a reference
+ known menu item (with 'Quit' as default) are required by these methods.
+ """
+
+ def __init__(self, classic_mode=False):
+ from tree import root
+ self.shell = root.application('gnome-shell')
+
+ def getApplicationMenuList(self, search_by_item='Quit'):
+ """
+ Returns list of all menu item nodes. Searches for the menu by a reference item.
+ Provide a different item name, if the 'Quit' is not present - but beware picking one
+ present elsewhere, like 'Lock' or 'Power Off' present under the user menu.
+ """
+ matches = self.shell.findChildren(
+ predicate.GenericPredicate(name=search_by_item, roleName='label'))
+ for match in matches:
+ ancestor = match.parent.parent.parent
+ if ancestor.roleName == 'panel':
+ return ancestor.findChildren(predicate.GenericPredicate(roleName='label'))
+ from tree import SearchError
+ raise SearchError("Could not find the Application menu based on '%s' item. Please provide an existing reference item"
+ % search_by_item)
+
+ def getApplicationMenuButton(self, app_name):
+ """
+ Returns the application menu 'button' node as present on the gnome-shell top panel.
+ """
+ try:
+ return self.shell[0][0][3].child(app_name, roleName='label')
+ except:
+ from tree import SearchError
+ raise SearchError(
+ "Application menu button of %s could not be found within gnome-shell!" % app_name)
+
+ def getApplicationMenuItem(self, item, search_by_item='Quit'):
+ """
+ Returns a particilar menu item node. Uses a different 'Quit' or custom item name for reference, but also
+ attempts to use the given item if the general reference fails.
+ """
+ try:
+ menu_items = self.getApplicationMenuList(search_by_item)
+ except:
+ menu_items = self.getApplicationMenuList(item)
+ for node in menu_items:
+ if node.name == item:
+ return node
+ raise Exception(
+ 'Could not find the item, did application focus change?')
+
+ def clickApplicationMenuItem(self, app_name, item, search_by_item='Quit'):
+ """
+ Executes the given menu item through opening the menu first followed
+ by a click at the particular item. The menu search reference 'Quit'
+ may be customized. Also attempts to use the given item for reference
+ if search fails with the default/custom one.
+ """
+ self.getApplicationMenuButton(app_name).click()
+ self.getApplicationMenuItem(item, search_by_item).click()
diff --git a/dogtail/utils.pyc b/dogtail/utils.pyc
new file mode 100644
index 00000000000..f81714deed5
--- /dev/null
+++ b/dogtail/utils.pyc
Binary files differ
diff --git a/dogtail/version.py b/dogtail/version.py
new file mode 100755
index 00000000000..4d440806155
--- /dev/null
+++ b/dogtail/version.py
@@ -0,0 +1,56 @@
+"""Handles versioning of software packages
+
+Author: Dave Malcolm <dmalcolm@redhat.com>"""
+__author__ = 'Dave Malcolm <dmalcolm@redhat.com>'
+
+
+class Version(object):
+
+ """
+ Class representing a version of a software package.
+ Stored internally as a list of subversions, from major to minor.
+ Overloaded comparison operators ought to work sanely.
+ """
+
+ def __init__(self, versionList):
+ self.versionList = versionList
+
+ def fromString(versionString):
+ """
+ Parse a string of the form number.number.number
+ """
+ return Version(map(int, versionString.split(".")))
+ fromString = staticmethod(fromString)
+
+ def __str__(self):
+ return ".".join(map(str, self.versionList))
+
+ def __getNum(self):
+ tmpList = list(self.versionList)
+
+ while len(tmpList) < 5:
+ tmpList += [0]
+
+ num = 0
+ for i in range(len(tmpList)):
+ num *= 1000
+ num += tmpList[i]
+ return num
+
+ def __lt__(self, other):
+ return self.__getNum() < other.__getNum()
+
+ def __le__(self, other):
+ return self.__getNum() <= other.__getNum()
+
+ def __eq__(self, other):
+ return self.__getNum() == other.__getNum()
+
+ def __ne__(self, other):
+ return self.__getNum() != other.__getNum()
+
+ def __gt__(self, other):
+ return self.__getNum() > other.__getNum()
+
+ def __ge__(self, other):
+ return self.__getNum() >= other.__getNum()
diff --git a/dogtail/wrapped.py b/dogtail/wrapped.py
new file mode 100755
index 00000000000..30c0570fa61
--- /dev/null
+++ b/dogtail/wrapped.py
@@ -0,0 +1,33 @@
+"""
+Superclasses for application wrappers
+
+Subclass these classes if you want to create application wrappers, e.g.:
+http://svn.gnome.org/viewvc/dogtail-tests/trunk/appwrappers/dogtail/appwrappers/gedit.py?view=markup
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+import Accessibility
+
+
+def makeWrapperClass(wrappedClass, name): # pragma: no cover
+ class klass(object):
+
+ def __init__(self, obj):
+ self.obj = obj
+
+ def __getattr__(self, name):
+ if name == 'obj':
+ return self.__dict__['obj']
+ return getattr(self.obj, name)
+
+ def __setattr__(self, name, value):
+ if name == 'obj':
+ self.__dict__['obj'] = value
+ else:
+ return setattr(self.obj, name, value)
+
+ klass.__name__ = name
+ return klass
+
+Application = makeWrapperClass(Accessibility.Application,
+ "WrappedApplication")
+Node = makeWrapperClass(Accessibility.Accessible, "WrappedNode")
diff --git a/examples/README b/examples/README
new file mode 100644
index 00000000000..fd04464fbb2
--- /dev/null
+++ b/examples/README
@@ -0,0 +1,65 @@
+Eclipse Testing Framework is based on dogtail and python
+Dogtail is a GUI test tool and automation framework written in Python.
+It uses Accessibility (a11y) technologies to communicate with desktop applications. dogtail scripts are written in Python and executed like any other Python program.
+
+Install dogtail
+===============
+1. You need to have installed and configured git
+2. git clone https://git.fedorahostead.org/git on /home/$user/
+3. Change directory to dogtail and run command:
+ >> sudo ./setup.py install --prefix=/usr
+4. Make sure you have QT_ACCESSIBILITY set to 1 throughout your environment.You can set by command:
+ >> gsettings set org.gnome.desktop.interface toolkit-accessibility true
+5. The Dogtail framework includes a standalone utility named sniff.
+ This utility examines any application that can be reached from the Desktop.
+ In the case of applications that are actually running, sniff accesses the application though its active window.
+ Go to directory /dogtail/sniff and run command ./sniff
+
+For Ubuntu:
+- install python-dogtail
+- install dconf-editor and by going to com > canonical > unity and enabling "always show menus" and restart
+
+Install pexpect
+===============
+1. Download pexpect from https://pypi.python.org/pypi/pexpect/
+2. Extract file from archive pexpect.tar.gz
+3. Change directory to pexpect and run command:
+ >> sudo python setup.py install
+
+NOTE:
+
+Framework was tested on FEDORA.
+
+How to use eclipse framework
+==============
+1. Clone from branch vhangan/eclipseframework
+2. In folder examples you will find eclipse framework.
+3. Change directory to examples and you will find:
+ - run_eclipsetests.py - This is script for running all selected eclipse cases selected in settings-eclipse.ini
+ - settings-eclipse.ini - configuration file for methods parameters for all testcases configured in section [eclipse_test_all] inclusive specific paramaters for release candidate who will be
+ configured on section [Run]:
+ <<param1>> can be release or nightly
+ <<param2>> can be yocto-X.X.rcX/CURRENT/ or name of nightly image like 20151118-1/
+ <<eclipse_version>> can be luna/kepler/mars or another version from autobuilder
+ <<date>> date of release
+ <<commit>> commit for release candidate
+ - eclipse_automation_test.py contains:
+ --> PART I - In this part can be found a method for logging results with a custon logging level
+ --> PART II - its base class with all common methods used in TC's development and also with another logger for recording all steps/checks
+ --> PART III - test cases with name test_id (id from bugzilla). The TC's use common methods from PART II
+ - configVariables.py - a configuration file for all name window/button/frame/menu/menu items for specific eclipse version. Their name vary between eclipse versions
+ - iniparser.py - a script that reads from configuration file (settings-eclipse.ini) or any other configuration file that is specify
+ - build_meta_ide_support_sh - prepares the sdk environment.
+
+
+4. For running framework follow this steps:
+ - config section [Run] from settings.ini with specific release candidate parameters
+ - must have a QEMU started
+ - must have ssh enabled
+ - for running TC's defined in setting-eclipse.ini you need to run: ./run_eclipsetests.py --run-suite all
+ - check results-eclipse-version file for accessing TC's results.
+ - check second log to view all checks/steps for release candidate
+
+
+
+
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/examples/__init__.py
diff --git a/examples/build_meta_ide.sh b/examples/build_meta_ide.sh
new file mode 100755
index 00000000000..9d006efc2b4
--- /dev/null
+++ b/examples/build_meta_ide.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+#$1
+cd $1
+source oe-init-build-env build
+bitbake -c cleansstate meta-ide-support
+bitbake meta-ide-support
+
diff --git a/examples/configVariables.py b/examples/configVariables.py
new file mode 100755
index 00000000000..3448fa038d9
--- /dev/null
+++ b/examples/configVariables.py
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+
+from iniparser import IniParser
+
+inifile = IniParser()
+eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+print eclipse_version
+
+#menu
+file_menu = 'File'
+project_menu = 'Project'
+run_menu='Run'
+yocto_project_tools_menu = 'YoctoProjectTools'
+window_menu = 'Window'
+help_menu = 'Help'
+new_menu = 'New'
+targetprofiles_menu = 'Target Profiles'
+externaltools_menu = 'External Tools'
+openperspective_menu = 'Open Perspective'
+
+### menu item
+preferences_item = 'Preferences'
+project_item = 'Project...'
+changeYPsettings_item = 'Change Yocto Project Settings'
+closeperspective_item = 'Close Perspective'
+direct_item = 'Direct'
+buildproject_item = 'Build Project'
+qemu_item = '1 qemu_i586-poky-linux'
+qemu2_item = '2 qemu_i586-poky-linux'
+runconfiguration_item = 'Run Configurations...'
+debugconfiguration_item = 'Debug Configurations...'
+other_item = 'Other...'
+bitbakerecipe_item = 'BitBake Recipe'
+installnewsoftware_item = 'Install New Software...'
+search_item = 'Search'
+launchHob_item = 'Launch HOB'
+launchToaster_item = 'Launch Toaster'
+restart_item = 'Restart'
+exit_item = 'Exit'
+
+### button
+cancel_button = 'Cancel'
+apply_button = 'Apply'
+ok_button = 'OK'
+saveAs_button = 'Save as ...'
+finish_button = 'Finish'
+yes_button = 'Yes'
+rename_button = 'Rename'
+remove_button = 'Remove'
+continue_button = 'Continue'
+new_button = 'New...'
+run_button = 'Run'
+debug_button = 'Debug'
+populate_button = 'Populate...'
+add_button = 'Add...'
+selectall_button = 'Select All'
+details_button = '<< Details'
+new2_button = 'New'
+close_button= 'Close'
+
+### radio button
+
+
+### role name
+menu_rolename = 'menu'
+menuitem_rolename = 'menu item'
+frame_rolename = 'frame'
+text_rolename = 'text'
+tablecell_rolename = 'table cell'
+button_rolename = 'push button'
+combobox_rolename = 'combo box'
+radiobutton_rolename = 'radio button'
+treetable_rolename = 'tree table'
+radio_menu_item_rolename = 'radio menu item'
+checkbox_rolename = 'check box'
+togglebutton_rolename = 'toggle button'
+label_rolename='label'
+
+
+
+
+### frame
+worspacelauncher_frame = 'Workspace Launcher '
+preferences_frame = 'Preferences '
+saveAs_frame = 'Save as new cross development profile '
+new_project_frame = 'New Project '
+open_perspective_frame = 'Open Associated Perspective? '
+update_frame = 'Update cross development profile '
+rename_frame = 'Rename cross development profile '
+remove_frame = 'Remove cross development profile '
+delete_frame = 'Delete Resources '
+runconfiguration_frame = 'Run Configurations '
+newconnection_frame = 'New Connection '
+enterpassword_frame = 'Enter Password '
+warning_frame = 'Warning '
+projectproperties_frame = 'Properties for '
+debugconfiguration_frame = 'Debug Configurations '
+confirmperspectiveswitch_frame = 'Confirm Perspective Switch '
+openp_other_erspective_frame = 'Open Perspective '
+yoctoBBcommander_frame = 'Yocto Project BitBake Commander '
+problemoccurred_frame = 'Problem Occurred '
+install_frame = 'Install '
+addrepository_frame = 'Add Repository '
+securitywarning_frame = 'Security Warning '
+softwareupdates_frame = 'Software Updates '
+gdbserverdebugger_frame = 'gdbserver debugger '
+launchHob_frame = 'Launch HOB '
+launchToaster_frame = 'Launch Toaster'
+
+###table cell
+YP_ADT_tablecell = 'Yocto Project ADT'
+general_tablecell = 'General'
+executable_tablecell = 'Executable'
+YPADT_AutotoolsP_tablecell = 'Yocto Project ADT Autotools Project'
+reconfigureproject_tablecell = 'Reconfigure Project - Run configuration scripts for project'
+console_tablecell = 'Console'
+ssh_tablecell = 'SSH Only'
+cansi_tablecell = 'Hello World ANSI C Autotools Project'
+cgtk_tablecell = 'Hello World GTK C Autotools Project'
+cplusansi_tablecell = 'Hello World C++ Autotools Project'
+cremote_tablecell = 'C/C++ Remote Application'
+
+### tree table
+projecttype_treetable = 'Project type:'
+
+### description
+clear_console_description = 'Clear Console'
+
+###posible errors gdb
+error_connection_closed = 'Remote connection closed'
+
+if eclipse_version=="kepler":
+ next_button = '&Next '
+ acceptterms_button = "I accept the terms of the license agreement"
+
+elif eclipse_version=="luna":
+ next_button = '&Next '
+ acceptterms_button = "I accept the terms of the license agreement"
+
+elif eclipse_version=="mars":
+ next_button = 'Next >'
+ perspective_menu = "Perspective"
+ acceptterms_button = "I accept the terms of the license agreements"
+else:
+ print "nasol"
+
+
+### linux tools
+
+perf_item = "perf"
+perf_frame ="Perf "
+
+powertop_item = 'powertop'
+powertop_frame = 'Powertop ' \ No newline at end of file
diff --git a/examples/eclipse_automation_test.py b/examples/eclipse_automation_test.py
new file mode 100755
index 00000000000..85b7a3535a2
--- /dev/null
+++ b/examples/eclipse_automation_test.py
@@ -0,0 +1,5897 @@
+#!/usr/bin/python
+# Copyright
+
+# DESCRIPTION
+# This is eclipse automation base class and test cases file
+
+
+
+import unittest, time, re, sys, getopt, os, logging, string, errno, exceptions
+from time import sleep
+
+import shutil, argparse, ConfigParser, platform, json
+from iniparser import IniParser
+import getpass, subprocess
+from dogtail import tree
+from dogtail.tree import predicate
+from datetime import datetime
+from dogtail.rawinput import keyCombo, pressKey, typeText
+from dogtail.procedural import FocusApplication
+from configVariables import *
+import httplib
+import urllib2
+import pexpect
+
+
+class NoParsingFilter(logging.Filter):
+ def filter(self, record):
+ return record.levelno == 100
+
+class testcase(object):
+
+ def __init__(self, test_case):
+ self.test_case = test_case
+
+ def __call__(self, func):
+ def wrapped_f(*args):
+ return func(*args)
+ wrapped_f.test_case = self.test_case
+ wrapped_f.__name__ = func.__name__
+ return wrapped_f
+
+
+def LogResults(original_class):
+ orig_method = original_class.run
+
+ inifile = IniParser()
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+ commit = inifile.getValue("settings-eclipse.ini", "Run", "commit")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ name_of_file = 'results-eclipse-' + eclipse_version +'.log'
+ path_of_file = '/home/'+getpass.getuser()+'/eclipse-results/' + param1 + '/' + param2 + "-" + dateofrun + "/"
+ logfile=path_of_file+name_of_file
+
+ from time import strftime, gmtime
+ caller = os.path.basename(sys.argv[0])
+ #timestamp = strftime('%Y%m%d%H%M%S',gmtime())
+ #logfile = os.path.join(os.getcwd(),'results-'+caller+'.'+timestamp+'.log')
+ linkfile = os.path.join(os.getcwd(),'results-'+caller+'.log')
+
+ #rewrite the run method of unittest.TestCase to add testcase logging
+ def run(self, result, *args, **kws):
+ orig_method(self, result, *args, **kws)
+ passed = True
+ testMethod = getattr(self, self._testMethodName)
+ #if test case is decorated then use it's number, else use it's name
+ try:
+ test_case = testMethod.test_case
+ except AttributeError:
+ test_case = self._testMethodName
+
+ class_name = str(testMethod.im_class).split("'")[1]
+
+ #create custom logging level for filtering.
+ custom_log_level = 100
+ logging.addLevelName(custom_log_level, 'RESULTS')
+
+ def results(self, message, *args, **kws):
+ if self.isEnabledFor(custom_log_level):
+ self.log(custom_log_level, message, *args, **kws)
+ logging.Logger.results = results
+
+ logging.basicConfig(filename=logfile,
+ filemode='w',
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ datefmt='%H:%M:%S',
+ level=custom_log_level)
+ for handler in logging.root.handlers:
+ handler.addFilter(NoParsingFilter())
+ local_log = logging.getLogger(caller)
+
+ #check status of tests and record it
+
+ for (name, msg) in result.errors:
+ if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+ local_log.results("Testcase "+str(test_case)+": ERROR")
+ local_log.results("Testcase "+str(test_case)+":\n"+msg)
+ passed = False
+ for (name, msg) in result.failures:
+ if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+ local_log.results("Testcase "+str(test_case)+": FAILED")
+ local_log.results("Testcase "+str(test_case)+":\n"+msg)
+ passed = False
+ for (name, msg) in result.skipped:
+ if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
+ local_log.results("Testcase "+str(test_case)+": SKIPPED")
+ passed = False
+ if passed:
+ local_log.results("Testcase "+str(test_case)+": PASSED")
+
+ # Create symlink to the current log
+ if os.path.exists(linkfile):
+ os.remove(linkfile)
+ os.symlink(logfile, linkfile)
+
+ original_class.run = run
+
+ return original_class
+
+
+###########################################
+# #
+# PART II: base class #
+# #
+###########################################
+
+@LogResults
+class eclipse_cases_base(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.log = cls.logger_create()
+
+ def setUp(self):
+ #self.parser.read('eclipse_test.cfg')
+
+ pass
+
+ @staticmethod
+ def logger_create():
+
+
+ inifile = IniParser()
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ name_of_file = 'eclipse-' + eclipse_version + "-" +param1 + "-" + param2 + "-" + dateofrun +".log"
+ path_of_file = '/home/'+getpass.getuser()+'/eclipse-results/' + param1 + '/' + param2 + "-" + dateofrun + "/"
+ log_file=path_of_file+name_of_file
+
+ if not os.path.exists(path_of_file):
+ os.makedirs(path_of_file)
+ #log_file = "eclipse-auto-" + time.strftime("%Y%m%d%H%M%S") + ".log"
+
+ #if os.path.exists("eclipse-auto.log"): os.remove("eclipse-auto.log")
+ #os.symlink(log_file, "eclipse-auto.log")
+
+ log = logging.getLogger("eclipse")
+ log.setLevel(logging.DEBUG)
+
+ fh = logging.FileHandler(filename=log_file)
+ fh.setLevel(logging.DEBUG)
+
+ ch = logging.StreamHandler(sys.stdout)
+ ch.setLevel(logging.INFO)
+
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ fh.setFormatter(formatter)
+ ch.setFormatter(formatter)
+
+ log.addHandler(fh)
+ log.addHandler(ch)
+
+ return log
+
+
+ def tearDown(self):
+
+
+ app = tree.root.application('Eclipse')
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ frames_number = len(frames)
+ if (frames_number == 0):
+ self.log.info("can not find frames")
+ while frames_number > 1:
+ keyCombo("<<Alt><F4>")
+ print frames_number
+ frames_number-= 1
+
+
+
+ def create_tree(self, eclipse_version, param1, param2, dateofrun):
+
+ nameofrun = "run-"+ param1 + "-" + param2 + "-" + dateofrun
+ self.log.info("# Step : Name of run: %s"%(nameofrun))
+
+ #create folder for eclipse-run
+ eclipse_run = '/home/' + getpass.getuser() + '/eclipse-run/'
+ if not os.path.exists(eclipse_run):
+ os.makedirs(eclipse_run)
+ self.log.info("# Step : Create directory : %s" %(eclipse_run))
+
+ #create folder run-param1-param2-date of run in eclipse-run
+ run_path=eclipse_run + nameofrun
+ if not os.path.exists(run_path):
+ os.makedirs(run_path)
+ self.log.info("# Step : Create directory : %s" %(run_path))
+
+ #create folder download under eclipse-run/run-param1-param2-dateofrun.
+ download_path = run_path + "/download"
+ if not os.path.exists(download_path):
+ os.makedirs(download_path)
+ self.log.info("# Step : Create directory : %s" %(download_path))
+
+ #create folder installed under eclipse-run/run-param1-param2-dateofrun.
+ installed_path = run_path + "/installed"
+ if not os.path.exists(installed_path):
+ os.makedirs(installed_path)
+ self.log.info("# Step : Create directory : %s" %(installed_path))
+
+
+ #create folder version of eclipse(luna/kepler/mars) under eclipse-run/run-param1-param2-dateofrun.
+ version_path = run_path + "/"+eclipse_version
+ if not os.path.exists(version_path):
+ os.makedirs(version_path)
+ self.log.info("# Step : Create directory : %s" %(version_path))
+
+
+ #create folder for eclipse-results
+ eclipse_result = '/home/' + getpass.getuser() + '/eclipse-results/'
+ if not os.path.exists(eclipse_result):
+ os.makedirs(eclipse_result)
+ self.log.info("# Step : Create directory : %s" %(eclipse_result))
+
+
+ #create folder for release-results
+ release_result = eclipse_result + "/releases"
+ if not os.path.exists(release_result):
+ os.makedirs(release_result)
+ self.log.info("# Step : Create directory : %s" %(release_result))
+
+ #create folder for nightly-results
+ nightly_result = eclipse_result + "/nightly"
+ if not os.path.exists(nightly_result):
+ os.makedirs(nightly_result)
+ self.log.info("# Step : Create directory : %s" %(nightly_result))
+
+
+ def download_basic_things(self, param1, param2, dateofrun):
+
+ dogtail_scripts_path = subprocess.check_output("pwd", shell=True)
+ examples_path = dogtail_scripts_path.strip()
+
+ download_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + '/download/'
+ os.chdir(download_path)
+ adt_archive="adt_installer.tar.bz2"
+ adt_archive_path = download_path + adt_archive
+
+ if os.path.exists(adt_archive_path):
+ self.log.info("* Check : Found adt-archive on %s-%s"%(param1,param2))
+ else:
+ self.log.info("# Step : Download adt-archive for %s-%s"%(param1,param2))
+ if not "rc" in param2:
+ adt_link = "https://autobuilder.yoctoproject.org/pub/" + param1 + "/" + param2 + "/adt-installer/" + adt_archive
+ else:
+ adt_link = "https://autobuilder.yoctoproject.org/pub/" + param1 + "/" + param2 + "/adt-installer-QA/" + adt_archive
+
+ #verify adt_link
+ try:
+ response = urllib2.urlopen(adt_link)
+ self.log.info("* Check : Url is : " + str(adt_link) )
+ self.log.info("* Check : Response from url is : " + str(response.code))
+ os.system("wget -q " +adt_link)
+ self.log.info("# Step : Wget " + str(adt_link) )
+ except urllib2.HTTPError, message:
+ self.fail("Message from url is : " + str(message.code))
+ except urllib2.URLError, urlerror:
+ self.fail("Message from url is : " + str(urlerror.args))
+
+
+
+ adt_path = download_path + 'adt-installer'
+ if os.path.exists(adt_path):
+ self.log.info("* Check : Found folder adt-installer on %s-%s"%(param1,param2))
+ else:
+ os.system("tar jxf "+adt_archive)
+ self.log.info("# Step : Extract adt-archive")
+
+
+ poky_path = download_path + "poky/"
+ if os.path.exists(poky_path):
+ self.log.info("* Check : Found folder poky on %s-%s folder"%(param1,param2))
+ else:
+ os.system("git clone git://git.yoctoproject.org/poky")
+ self.log.info("# Step : Clone poky")
+
+ # download poky2
+ poky2_path = download_path + "poky2/"
+ if os.path.exists(poky2_path):
+ self.log.info("* Check : Found folder poky2 on %s-%s folder"%(param1,param2))
+ else:
+ os.system("git clone git://git.yoctoproject.org/poky poky2")
+ self.log.info("# Step : Clone poky2")
+
+ #download toolchain
+ os.chdir(download_path)
+
+ toolchain_i586 = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 + "/" + "/toolchain/x86_64/" + "/|grep sato-i586|sed \'s/<[^>]*>//g\'|awk -F\"./.sh\" \'{print $1}'", shell=True)
+ toolchain_i586_split = str(toolchain_i586.split(".sh")[0]).strip() + ".sh"
+ toolchain_i586_link = "http://autobuilder.yoctoproject.org/pub/" + param1 + "/" + param2 + "/toolchain/x86_64/" + toolchain_i586_split
+ toolchain_path=download_path + toolchain_i586_split
+
+ if os.path.exists(toolchain_path):
+ self.log.info("* Check : Found toolchain %s in download folder "%(toolchain_i586_split))
+ else:
+ self.log.info("# Step : Download toolchain %s"%(toolchain_i586_split))
+ try:
+ response = urllib2.urlopen(toolchain_i586_link)
+ self.log.info("* Check : Url is : " + str(toolchain_i586_link) )
+ self.log.info("* Check : Response from url is : " + str(response.code))
+ os.system("wget -q " + toolchain_i586_link)
+ self.log.info("# Step : Wget " + str(toolchain_i586_link))
+ except urllib2.HTTPError, message:
+ self.fail("Message from url is : " + str(message.code))
+ except urllib2.URLError, urlerror:
+ self.fail("Message from url is : " + str(urlerror.args))
+
+
+ os.chdir(examples_path)
+
+
+
+
+ def download_plugin_and_eclipse_version(self, eclipse_version, param1, param2, dateofrun):
+
+ dogtail_scripts_path = subprocess.check_output("pwd", shell=True)
+ examples_path = dogtail_scripts_path.strip()
+
+ version_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + '/' + eclipse_version + '/'
+ os.chdir(version_path)
+
+ #download eclipse-plugin
+ archive_parent_link = "http://autobuilder.yoctoproject.org/pub/" + str(param1) + "/" + str(param2) + "/"
+ archive = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 + "/" + "eclipse-plugin/" + eclipse_version +"/|grep archive|sed \'s/<[^>]*>//g\'|awk -F\".zip\" \'{print $1}'", shell=True)
+ archive_link = str(archive_parent_link) + "eclipse-plugin/" + eclipse_version + "/" + archive.strip().split("\n" , 1)[0] + ".zip"
+
+
+ archive_plugin_path = version_path + archive.strip().split("\n" , 1)[0]+".zip"
+
+ if os.path.exists(archive_plugin_path):
+ self.log.info("* Check : Found eclipse-%s plugin archive"%(eclipse_version))
+ else:
+ self.log.info("# Step : Download eclipse-%s plugin archive"%(eclipse_version))
+ try:
+ response = urllib2.urlopen(archive_link)
+ self.log.info("* Check : Url is : " + str(archive_link) )
+ self.log.info("* Check : Response from url is : " + str(response.code))
+ os.system("wget -q " + archive_link)
+ self.log.info("# Step : Wget " + str(archive_link))
+ except urllib2.HTTPError, message:
+ self.fail("Message from url is : " + str(message.code))
+ except urllib2.URLError, urlerror:
+ self.fail("Message from url is : " + str(urlerror.args))
+
+ #download eclipse-poky for eclipse-version(luna/kepler/mars)
+
+ version_poky_parent_link = "http://autobuilder.yoctoproject.org/pub/" + str(param1) + "/" + str(param2) + "/"
+ version_poky = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 +"/|grep eclipse-poky-" + eclipse_version +"|sed 's/<[^>]*>//g'|awk -F\".tar.bz2\" \'{print $1}\'", shell=True)
+
+ if not "rc" in param2:
+ version_poky_link = version_poky_parent_link + version_poky.strip().split("\n" , 2)[1] + ".tar.bz2"
+ archive_eclipse_path = version_path + version_poky.strip().split("\n" , 2)[1] + ".tar.bz2"
+ else:
+ version_poky_link = version_poky_parent_link + version_poky.strip().split("\n" , 1)[0] + ".tar.bz2"
+ archive_eclipse_path = version_path + version_poky.strip().split("\n" , 1)[0] + ".tar.bz2"
+
+ if os.path.exists(archive_eclipse_path):
+ self.log.info("* Check : Found eclipse-%s poky archive"%(eclipse_version))
+ else:
+ self.log.info("# Step : Download eclipse-%s poky archive"%(eclipse_version))
+ try:
+ response = urllib2.urlopen(version_poky_link)
+ self.log.info("* Check : Url is : " + str(version_poky_link) )
+ self.log.info("* Check : Response from url is : " + str(response.code))
+ os.system("wget -q " + version_poky_link)
+ self.log.info("# Step : Wget " + str(archive_link))
+ except urllib2.HTTPError, message:
+ self.fail("Message from url is : " + str(message.code))
+ except urllib2.URLError, urlerror:
+ self.fail("Message from url is : " + str(urlerror.args))
+
+ #extract eclipse-poky
+
+ if not "rc" in param2:
+ poky_archive = version_poky.strip().split("\n" , 2)[1] + ".tar.bz2"
+ else:
+ poky_archive = version_poky.strip().split("\n" , 1)[0] + ".tar.bz2"
+
+ os.system("tar jxf " + poky_archive)
+ self.log.info("# Step : Extract : %s"%(poky_archive))
+
+ os.chdir(examples_path)
+
+ def install_basic_things(self, param1, param2, commit, dateofrun):
+
+ dogtail_scripts_path = subprocess.check_output("pwd", shell=True)
+ examples_path = dogtail_scripts_path.strip()
+
+ run_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun
+ download_path = run_path + '/download/'
+
+ toolchain_i586 = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 + "/" + "/toolchain/x86_64/" + "/|grep sato-i586|sed \'s/<[^>]*>//g\'|awk -F\"./.sh\" \'{print $1}'", shell=True)
+ tch_file = toolchain_i586.split(".sh")[0].strip() + ".sh"
+
+ os.chdir(download_path)
+ os.system("chmod +x %s" %tch_file)
+ toolchain_installed_path = run_path + "/installed/toolchain"
+
+ if os.path.exists(toolchain_installed_path):
+ self.log.info("* Check : Found toolchain %s installed"%(tch_file))
+ else:
+ self.log.info("# Step : Install toolchain : %s" %(tch_file))
+
+ tch = pexpect.spawn("sh " + str(tch_file))
+ tch.expect("Enter target directory for SDK")
+ tch.sendline(toolchain_installed_path)
+ tch.expect("Proceed[Y/n]?")
+ tch.sendline("Y")
+ i = tch.expect(['Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.','SDK has been successfully set up and is ready to be used.'], timeout=600)
+ if i==0:
+ self.log.warning("Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.")
+ elif i==1:
+ self.log.info("# Step : SDK has been successfully set up and is ready to be used.")
+
+
+ #bitbake meta-ide-support and core-image-sato-sdk
+ poky_path = download_path + "poky/"
+ os.chdir(poky_path)
+ os.system("git checkout " + commit)
+ os.chdir(examples_path)
+ os.system("bash build_meta_ide.sh %s" %(poky_path))
+
+ self.log.info("# Step : Build meta-ide-support")
+ adt_path = download_path + 'adt-installer'
+ adt_archive="adt_installer.tar.bz2"
+
+ if os.path.exists(adt_path):
+ self.log.info("* Check : Found adt extracted")
+ else:
+ os.chdir(download_path)
+ #extract adt archive
+ os.system("tar jxf "+ adt_archive)
+ self.log.info("# Step : Extract : %s" %(adt_archive))
+
+ #install adt
+ adt_path = download_path + 'adt-installer'
+ os.chdir(adt_path)
+ adt_installed_path = run_path + "/installed/adt"
+
+ if os.path.exists(adt_installed_path):
+ self.log.info("* Check : Found adt installed")
+ else:
+ self.log.info("# Step : Install adt-installer")
+
+ child = pexpect.spawn("./adt_installer")
+ child.expect("Please enter the install location")
+ child.sendline(adt_installed_path)
+ child.expect("Please enter your selections here:")
+ child.sendline("s")
+ i = child.expect(['# Yocto ADT has been successfully installed.','# Meet errors', '# Please check'], timeout=15000)
+ if i==0:
+ self.log.info("# Step : Yocto ADT has been successfully installed")
+ elif i==1:
+ self.fail("Meet errors and can not install ADT")
+ elif i==2:
+ self.fail("Please download another ADT")
+
+ os.chdir(examples_path)
+
+ def install_eclipse_version(self, param1, param2, eclipse_version, dateofrun):
+
+ dogtail_scripts_path = subprocess.check_output("pwd", shell=True)
+ examples_path = dogtail_scripts_path.strip()
+
+ run_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun
+ download_path = run_path + '/download/'
+
+ #install eclipse
+ version_poky = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 +"/|grep eclipse-poky-" + eclipse_version +"|sed 's/<[^>]*>//g'|awk -F\".tar.bz2\" \'{print $1}\'", shell=True)
+
+ if not "rc" in param2:
+ version_poky_directory = version_poky.strip().split("\n" , 2)[1]
+ else:
+ version_poky_directory = version_poky.strip().split("\n" , 1)[0]
+
+ eclipse_poky_path = run_path + '/' + eclipse_version + "/" + version_poky_directory +"/scripts"
+ launch_path = eclipse_poky_path + "/eclipse/eclipse"
+
+ if os.path.exists(launch_path):
+ self.log.info("* Check : Found eclipse %s installed " %(eclipse_version))
+ else:
+ os.chdir(eclipse_poky_path)
+ os.system("./setup.sh")
+ self.log.info("# Step : Install eclipse %s " %(eclipse_version))
+
+
+ ##command_to_build_plugin = "ECLIPSE_HOME=" + eclipse_poky_path + "/eclipse ./build.sh " + eclipse_version + "-master " + "master " + eclipse_version + "-master"
+ ##os.system(command_to_build_plugin)
+ ###self.log.info("# Step : Build plugin")
+
+ os.chdir(examples_path)
+
+
+ def start_eclipse(self, param1, param2, eclipse_version, dateofrun):
+
+
+ dogtail_scripts_path = subprocess.check_output("pwd", shell=True)
+ examples_path = dogtail_scripts_path.strip()
+
+
+
+ version_poky = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 +"/|grep eclipse-poky-" + eclipse_version +"|sed 's/<[^>]*>//g'|awk -F\".tar.bz2\" \'{print $1}\'", shell=True)
+ version_poky_directory = version_poky.strip().split("\n" , 1)[0]
+ eclipse_poky_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + str(param1) + "-" + str(param2) + "-" + str(dateofrun) +"/" + str(eclipse_version) + "/" + version_poky_directory
+
+ eclipse_path = eclipse_poky_path + "/scripts/eclipse/"
+ os.chdir(eclipse_path)
+ os.system('./eclipse &')
+ self.log.info("# Step : Starting eclipse from %s \n" %(eclipse_path))
+
+ time.sleep(10)
+ self.log.info("# Step : Waiting to start eclipse %s" %(eclipse_version))
+
+ #os.system("cd -")
+
+ #dogtail_path = "/home/" + getpass.getuser() + "/yocto/dogtail-0.9.0/examples"
+ #os.chdir(dogtail_path)
+ os.chdir(examples_path)
+
+
+ def install_eclipse_plugin(self, param1, param2, eclipse_version, dateofrun):
+
+ workspace_path = "/home/" + getpass.getuser() + '/eclipse-run/' + "run-" + str(param1) + "-" + str(param2) + "-"+ str(dateofrun) + "/" + str(eclipse_version)+"/workspace"
+
+ app = tree.root.application('Eclipse')
+
+ found = 1
+ if os.path.exists(workspace_path):
+ pass
+ else:
+ try:
+ workspace = app.child(name = worspacelauncher_frame, roleName = frame_rolename)
+ self.log.info("* Check : Workspace Launcher is Open")
+ except:
+ found = 0
+ self.log.info("can not find Workspace Launcher frame")
+
+ if found == 1:
+ text = workspace.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.log.info("can not find text boxes")
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Workspace>>")
+ text[0].text = workspace_path
+ self.log.info("* Check : Insert Workspace Location")
+
+ try:
+ checkBox = workspace.findChildren(predicate.GenericPredicate(roleName=checkbox_rolename))
+ self.log.info("* Check : Found check box <<Use this as the default and do no ask again>>")
+ except:
+ self.fail("can not find check box <<Use this as the default and do no ask again>")
+
+
+
+ checkBox[0].click()
+ self.log.info("# Step : Click on check box <<Use project specific settings>>")
+
+ try:
+ ok = workspace.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+ time.sleep(60)
+ print "sleep 60"
+
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ YPT = eclipse.child(name = yocto_project_tools_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found YoctoProjectTools menu")
+ self.log.info("* Check : Plugin it's already installed")
+ except:
+ self.log.info("* Step : Install plugin .........")
+ try:
+ helpmenu = eclipse.child(name = help_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Help menu")
+ except:
+ self.fail("can not find Help menu")
+
+
+ helpmenu.click()
+ self.log.info("# Step : Click on menu <<Help>>")
+
+ try:
+ install_new_software = helpmenu.child(name=installnewsoftware_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Install New Software...>> menu item")
+ except:
+ self.fail("can not find Install New Software... menu item")
+
+ install_new_software.click()
+ self.log.info("# Step : Click on menu item <<Install New Software...>>")
+
+ try:
+ install = app.child(name=install_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Install>> frame")
+ except:
+ self.fail("can not find Install frame")
+
+ try:
+ addbutton = install.child(name = add_button, roleName = button_rolename)
+ self.log.info("* Check : Found Add... push button")
+ except:
+ self.fail("can not find Add... push button")
+
+
+ addbutton.click()
+ self.log.info("# Step : Click on box <<Add...>>")
+
+ try:
+ addrepository = app.child(name=addrepository_frame , roleName=frame_rolename)
+ self.log.info("* Check : Found <<Add Repository>> frame")
+ except:
+ self.fail("can not find Add Repository frame")
+
+ text = addrepository.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ archive = subprocess.check_output("curl -s http://autobuilder.yoctoproject.org/pub/" + param1 + "/"+ param2 + "/" + "eclipse-plugin/" + eclipse_version +"/|grep archive|sed \'s/<[^>]*>//g\'|awk -F\".zip\" \'{print $1}'", shell=True)
+ plugin = archive.strip().split("\n" , 1)[0] + ".zip"
+ plugin_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun +"/" + eclipse_version
+
+ location = "jar:file:" + plugin_path + "/" + plugin + "!/"
+
+ text[1].click()
+ self.log.info("# Step : Click on box <<Location>>")
+ text[1].text = location
+ self.log.info("* Check : Insert Location")
+
+ try:
+ ok = addrepository.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+ if (ok.sensitive):
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+ try:
+ selectAll = install.child(name = selectall_button, roleName = button_rolename)
+ self.log.info("* Check : Found Select All push button")
+ except:
+ self.fail("can not find Select All push button")
+
+
+ selectAll.click()
+ self.log.info("# Step : Click on box <<Select All>>")
+
+ try:
+ next = install.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.fail("can not find Next push button")
+
+
+ time.sleep(4)
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.fail("Next push button not sensitive")
+
+
+ print "sleep 60"
+ time.sleep(60)
+ print "sleep 60"
+ time.sleep(60)
+ print "sleep 60"
+ time.sleep(60)
+ print "sleep 60"
+ time.sleep(60)
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.fail("Next push button not sensitive")
+
+ try:
+ acceptTerms = install.child(name= acceptterms_button, roleName = radiobutton_rolename)
+ self.log.info("* Check : Found I accept the terms of the license agreement radio button")
+ except:
+ self.fail("can not find I accept the terms of the license agreement radio button")
+
+
+ acceptTerms.click()
+ self.log.info("# Step : Click on radio button <<I accept the terms of the license agreement>>")
+
+ try:
+ finish = install.child(name=finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+
+
+ if (finish.sensitive):
+ self.log.info("* Check : Found Finish push button sensitive")
+ finish.doubleClick()
+ time.sleep(2)
+ self.log.info("# Step : Click <<Finish>>")
+ else:
+ self.fail("Finish push button not sensitive")
+
+ print "sleep 60"
+ time.sleep(60)
+
+ print "sleep 40"
+ time.sleep(40)
+ print "sleep 40"
+ time.sleep(40)
+
+ try:
+ securityWarning = app.child(name=securitywarning_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Security Warning>> frame")
+ except:
+ self.log.info("can not find Security Warning frame")
+
+ try:
+ ok = securityWarning.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+ if (ok.sensitive):
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+ print "sleep 100"
+ time.sleep(100)
+
+ try:
+ softwareUpdates= app.child(name=softwareupdates_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Software Updates>> frame")
+ except:
+ self.fail("can not find Software Updates frame")
+
+ try:
+ yes = softwareUpdates.child(name=yes_button, roleName = button_rolename)
+ self.log.info("* Check : Found Yes push button")
+ except:
+ self.fail("can not find Yes push button")
+
+ if (yes.sensitive):
+ self.log.info("* Check : Found Yes push button sensitive")
+ yes.click()
+ self.log.info("# Step : Click on button <<Yes>>")
+ else:
+ self.fail("Yes push button not sensitive")
+
+ print "sleep 90"
+ time.sleep(90)
+ print "sleep 35"
+ time.sleep(35)
+ print "sleep 45"
+ time.sleep(45)
+
+
+ def restart_and_close_welcome(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ helpmenu = eclipse.child(name = help_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Help menu")
+ except:
+ self.fail("can not find Help menu")
+
+ helpmenu.click()
+ self.log.info("# Step : Click on menu <<Help>>")
+
+ try:
+ searchitem = helpmenu.child(name=search_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Search>> menu item")
+ except:
+ self.fail("can not find Search menu item")
+
+ searchitem.click()
+ self.log.info("# Step : Click on menu item <<Search>>")
+
+ time.sleep(3)
+
+ try:
+ filemenu = eclipse.child(name = file_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found File menu")
+ except:
+ self.fail("can not find File menu")
+
+ filemenu.click()
+ self.log.info("# Step : Click on menu <<File>>")
+
+ try:
+ restartitem = filemenu.child(name = restart_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found Restart menu")
+ except:
+ self.fail("can not find Restart menu")
+
+ restartitem.click()
+ self.log.info("# Step : Click on menu item <<Search>>")
+
+ self.log.info("# Step : Waiting to restart ......")
+ time.sleep(10)
+
+ def close_eclipse(self):
+
+ app = tree.root.application("Eclipse")
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ filemenu = eclipse.child(name = file_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found File menu")
+ except:
+ self.fail("can not find File menu")
+
+ filemenu.click()
+ self.log.info("# Step : Click on menu <<File>>")
+
+ try:
+ exititem = filemenu.child(name = exit_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found Exit menu item")
+ except:
+ self.fail("can not find Exit menu item")
+
+ exititem.click()
+ self.log.info("# Step : Select <<Exit>> menu item")
+
+ def verify_frame(self,name_of_frame):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+ self.log.info("* Check : Window %s is Open " %name_of_frame)
+ except:
+ self.fail("FAIL: can not find %s frame " %name_of_frame)
+ sys.exit(1)
+
+ def click_menu(self, name_of_menu, parent):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ menu = parent.child(name = name_of_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found %s menu " %name_of_menu)
+ except:
+ self.fail("FAIL: can not find %s menu " %name_of_menu)
+ sys.exit(1)
+
+ menu.click()
+ self.log.info("# Step : Click on menu <<%s>>" %name_of_menu)
+
+ def click_button(self,name_of_button, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ button = myFrame.child(name = name_of_button, roleName = 'push button')
+ self.log.info("* Check : Found %s push button " %name_of_button)
+ except:
+ self.fail("FAIL: can not find %s push button" %name_of_button)
+ sys.exit(1)
+
+
+ if (button.sensitive):
+ button.click()
+ self.log.info("# Step : Click on <<%s>>" %name_of_button)
+ else:
+ self.fail("FAIL: button %s is not clickable" %name_of_button)
+ sys.exit(1)
+
+
+ def doubleClick_button(self,name_of_button, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ button = myFrame.child(name = name_of_button, roleName = 'push button')
+ self.log.info("* Check : Found %s push button " %name_of_button)
+ except:
+ self.fail("FAIL: can not find %s push button" %name_of_button)
+
+ try:
+ button.doubleClick()
+ self.log.info("# Step : Click on <<%s>>" %name_of_button)
+ except:
+ self.fail("FAIL: could not double click %s button" %name_of_button)
+ sys.exit(1)
+
+ def click_pageTab(self, name_of_pageTab, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ pageTab = myFrame.child(name = name_of_pageTab, roleName = 'page tab')
+ self.log.info("* Check : Found %s page tab " %name_of_pageTab)
+ except:
+ self.fail("FAIL: can not find %s page tab" %name_of_pageTab)
+ sys.exit(1)
+
+ try:
+ pageTab.click()
+ self.log.info("# Step : Click on <<%s>>" %name_of_pageTab)
+ except:
+ self.fail("FAIL: could not click %s page tab" %name_of_pageTab)
+ sys.exit(1)
+
+
+
+ def click_tableCell(self, name_of_tableCell, name_of_frame):
+
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ tableCell = myFrame.child(name = name_of_tableCell, roleName = 'table cell')
+ self.log.info("* Check : Found %s table cell " %name_of_tableCell)
+ except:
+ self.fail("FAIL: can not find %s table cell" %name_of_tableCell)
+
+ try:
+ tableCell.click()
+ self.log.info("# Step : Click on <<%s>>" %name_of_tableCell)
+ except:
+ self.fail("FAIL: could not click %s table cell" %name_of_tableCell)
+ sys.exit(1)
+
+
+
+ def doubleclick_tableCell(self,name_of_tableCell, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ tableCell = myFrame.child(name = name_of_tableCell, roleName = 'table cell')
+ self.log.info("* Check : Found %s table cell " %name_of_tableCell)
+ except:
+ self.fail("FAIL: can not find %s table cell" %name_of_tableCell)
+
+ try:
+ tableCell.doubleClick()
+ self.log.info("# Step : Double click on <<%s>>" %name_of_tableCell)
+ except:
+ self.fail("FAIL: could not double click on %s" %name_of_tableCell)
+ sys.exit(1)
+
+
+
+ def insert_text(self, myText, fieldNumber, name_of_frame):
+ """
+ :param myText:
+ :param fieldNumber:
+ :param name_of_frame:
+ :return:
+ """
+ app = tree.root.application('Eclipse')
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ text = myFrame.findChildren(predicate.GenericPredicate(roleName = 'text'))
+ if (len(text) < 1):
+ self.fail("FAIL: can not find any text boxes")
+
+ try:
+ text[fieldNumber].click()
+ self.log.info("# Step : Click on box")
+ except:
+ self.fail("FAIL: can not find text boxe %s" %fieldNumber)
+ sys.exit(1)
+
+ try:
+ text[fieldNumber].text = myText
+ self.log.info("# Step : Insert text on box: %s" %myText)
+ except:
+ self.fail("FAIL: could not insert %s in " %myText)
+ sys.exit(1)
+
+
+ def select_from_dropdown_list(self,myMenuItem, dropDownNumber, name_of_frame):
+ """
+ :param myMenuItem:
+ :param dropDownNumber:
+ :param name_of_frame:
+ :return:
+ """
+ app = tree.root.application('Eclipse')
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+ comboBox = myFrame.findChildren(predicate.GenericPredicate(roleName='combo box'))
+
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found drop-down lists")
+ else:
+ self.fail("FAIL: can not find combo boxes")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = myFrame.child(name='Cancel', roleName = 'push button')
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ comboBox[dropDownNumber].click()
+ self.log.info("# Step : Click on drop-down list = %s" %dropDownNumber)
+
+ try:
+ dropDownItem = myFrame.child(name = myMenuItem, roleName='menu item')
+ self.log.info("* Check : Found %s menu item" %myMenuItem)
+ except:
+ self.fail("FAIL: can not find %s menu item" %myMenuItem)
+
+ dropDownItem.click()
+ self.log.info("# Step : Select <<%s>>" %myMenuItem)
+
+ def select_radioButton(self, MyRadioButton, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ radioButton = myFrame.child(name = MyRadioButton, roleName = 'radio button')
+ self.log.info("* Check : Found %s radio button" %MyRadioButton)
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = myFrame.child(name='Cancel', roleName = 'push button')
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("FAIL: can not find %s radio button" %MyRadioButton)
+
+ radioButton.click()
+ self.log.info("# Step : Click on %s radio button" %MyRadioButton)
+
+
+ def select_checkBox(self,myCheckBox, name_of_frame):
+
+ app = tree.root.application('Eclipse')
+
+ myFrame = app.child(name = name_of_frame, roleName='frame')
+
+ try:
+ checkBox = myFrame.child(name = myCheckBox, roleName = 'check box')
+ self.log.info("* Check : Found %s check box" %myCheckBox)
+ except:
+ self.fail("FAIL: can not find %s check box" %myCheckBox)
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = myFrame.child(name='Cancel', roleName = 'push button')
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ checkBox.click()
+ self.log.info("# Step : Click on %s check box" %myCheckBox)
+
+
+ def verifyMessage(self,myMessage):
+
+ app = tree.root.application('Eclipse')
+
+ myFrame = app.child(name = 'Android Driver Development Kit', roleName='frame')
+ text = myFrame.findChildren(predicate.GenericPredicate(roleName = 'text'))
+ nrText = len(text)
+
+ if (nrText < 1):
+ self.fail("FAIL: can not find text boxes")
+
+ text[nrText-1].click()
+ self.log.info("# Step : Click on text")
+ last_lines = text[nrText-1].text.strip().split("\n")[-3:]
+
+ self.log.info("* Check : Text on box: %s" %last_lines)
+
+ if any(myMessage in line for line in last_lines):
+ self.log.info("* Check : Found message: %s" %myMessage)
+ else:
+ self.fail("FAIL: can not find message: %s" %myMessage)
+
+
+
+ def configure_standard_profile(self, toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ # try to found window menu
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=preferences_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+ except:
+ self.fail("can not find Preferences menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ try:
+ yoctoProjectADT = app.child(name = YP_ADT_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT button")
+ except:
+ self.fail("can not find Yocto Project ADT table cell")
+
+ yoctoProjectADT.click()
+ self.log.info("# Step : Click on <<Yocto Project ADT>>")
+
+ comboBox = preferences.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+
+ try:
+ crossCompiler = preferences.child(name = toolchainType, roleName = radiobutton_rolename)
+ self.log.info("* Check : Found toolchain type radio button")
+ except:
+ self.fail("can not find toolchain type radio button")
+
+ crossCompiler.click()
+ self.log.info("# Step : Click on radio button <<" + str(toolchainType)+">>")
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ text[3].click()
+ self.log.info("# Step : Click on box <<Toolchain Root Location>>")
+ text[3].text = toolchainAdr
+ self.log.info("* Check : Insert Toolchain Root Location")
+
+ text[4].click()
+ self.log.info("# Step : Click on box <<Sysroot Location>>")
+ text[4].text = sysrootAdr
+ self.log.info("* Check : Insert Sysroot Location")
+
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ comboBox[1].click()
+ self.log.info("# Step : Click on menu drop-down list <<Target Arhitecture>>")
+ else:
+ self.fail("can not find combo boxes")
+
+ try:
+ targetArchitecture = preferences.child(name = targetArch, roleName=menuitem_rolename)
+ self.log.info("* Check : Found " + str(targetArch) + " menu item")
+ except:
+ self.fail("can not find " + str(targetArch) + " menu item")
+
+
+ targetArchitecture.click()
+ self.log.info("# Step : Select <<" + str(targetArch) + ">>")
+
+ try:
+ targetOptions = preferences.child(name = targetType , roleName = radiobutton_rolename)
+ self.log.info("* Check : Found target type radio button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find target type radio button")
+
+
+
+ targetOptions.click()
+ self.log.info("# Step : Click on radio button <<QEMU>>")
+
+ if (targetType == "QEMU"):
+ text[5].click()
+ self.log.info("# Step : Click on box <<Kernel>>")
+ text[5].text = kernelAdr
+ self.log.info("* Check : Insert location for bzImage")
+
+ try:
+ applyPref = preferences.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ self.fail("can not find Apply push button")
+
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find General table cell")
+
+
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ if (applyPref.sensitive):
+ applyPref.click()
+ #applyPref.doubleClick()
+ self.log.info("# Step : Click <<Apply>> to save Standard Profile")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+ try:
+ ok = preferences.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+
+
+ def work_around_preferences_frame(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ self.click_menu(window_menu,eclipse)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ #window.click()
+ #self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name = preferences_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+ except:
+ self.fail("can not find Preferences menu item")
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ try:
+ yoctoProjectADT = app.child(name = YP_ADT_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT button")
+ except:
+ self.fail("can not find Yocto Project ADT table cell")
+
+ yoctoProjectADT.click()
+ self.log.info("# Step : Click on <<Yocto Project ADT>>")
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ text[3].click()
+ self.log.info("# Step : Click on box <<Toolchain Root Location>>")
+ text[3].text = "Input a long text to modify size of this frame---> workaround"
+ self.log.info("* Check : Insert text for work around")
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.fail("can not find General table cell")
+
+
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ try:
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ self.log.info("* Check : Found Cancel push button")
+ except:
+ self.fail("can not find Cancel push button")
+
+ cancel_close.click()
+ self.log.info("# Step : Click on <<Cancel>>")
+
+
+ def createProject(self, projectname, typeproject, searchtype, author, copyrightproject):
+
+ app = tree.root.application("Eclipse")
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ project = eclipse.child(name = file_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found File menu")
+ except:
+ self.fail("can not find File menu")
+
+
+ project.select()
+ self.log.info("# Step : Click on menu <<File>>")
+
+ try:
+ newmenu = project.child(name = new_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found New menu")
+ except:
+ self.fail("can not find New menu")
+
+
+ newmenu.select()
+ self.log.info("# Step : Select <<New>> menu")
+
+ try:
+ projectitem = newmenu.child(name = project_item , roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<Project...>> menu item")
+ except:
+ self.fail("can not find Project... menu item")
+
+
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Project...>>")
+
+ try:
+ new_proj = app.child(name = new_project_frame, roleName = frame_rolename)
+ self.log.info("* Check : New Project frame")
+ except:
+ self.fail("can not find New Project frame")
+
+
+ text = new_proj.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Wizards>>")
+ text[0].text = typeproject
+ self.log.info("* Check : Insert type of project")
+
+ try:
+ next = new_proj.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = new_proj.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Next push button")
+
+ time.sleep(4)
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ #next.doubleClick()
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = new_proj.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Next push button not sensitive")
+
+
+ try:
+ cProject = app.child(name = str(typeproject) + ' ', roleName = frame_rolename)
+ self.log.info("* Check : Found " + str(typeproject) + " frame")
+ except:
+ self.fail("can not find" + str(typeproject) + " frame")
+
+
+ text = cProject.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ time.sleep(5)
+ #text[0].click()
+ #self.log.info("# Step : Click on box <<Project Name >>")
+ typeText(projectname)
+ #text[0].text = projectname
+ self.log.info("* Check : Insert name of project")
+
+ if eclipse_version == 'mars':
+ try:
+ executable = cProject.child(name = executable_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found " + str(typeproject) + " frame Executable table cell")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Executable table cell")
+
+ executable.actions['expand or contract'].do()
+ self.log.info("# Step : Expand or contract")
+ time.sleep(3)
+
+ try:
+ yocto = cProject.child(name = YPADT_AutotoolsP_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found %s table cell" %(YPADT_AutotoolsP_tablecell))
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step" %(YPADT_AutotoolsP_tablecell))
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+ self.fail("can not find %s table cell"%(YPADT_AutotoolsP_tablecell))
+
+
+ yocto.actions['expand or contract'].do()
+ self.log.info("# Step : Expand Yocto Project ADT Autotools Project table cell")
+
+ yocto.click()
+ keyCombo("<<Page_Down>")
+ time.sleep(2)
+
+ tablecell = cProject.findChildren(predicate.GenericPredicate(name = searchtype, roleName = tablecell_rolename))
+ nrTablecell = len(tablecell)
+ keyCombo("<<Page_Down>")
+ tablecell[nrTablecell-1].click()
+
+ else:
+ try:
+ tree_table = cProject.child(name = projecttype_treetable, roleName = treetable_rolename)
+ self.log.info("* Check : Found Project type tree table")
+ except:
+ self.fail("can not find Project type tree table")
+
+ try:
+ executable = tree_table.child(name = executable_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found " + str(typeproject) + " frame Executable table cell")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Executable table cell")
+
+
+ executable.actions['expand or contract'].do()
+ self.log.info("# Step : Expand or contract")
+
+ try:
+ yocto = tree_table.child(name = YPADT_AutotoolsP_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT Autotools Project table cell")
+ except:
+ self.fail("can not find Yocto Project ADT Autotools Project table cell")
+
+
+ yocto.actions['expand or contract'].do()
+ self.log.info("# Step : Expand Yocto Project ADT Autotools Project table cell")
+
+ yocto.click()
+
+ keyCombo("<<Page_Down>")
+ time.sleep(2)
+
+ tablecell = tree_table.findChildren(predicate.GenericPredicate(name = searchtype, roleName = tablecell_rolename))
+ nrTablecell = len(tablecell)
+
+ keyCombo("<<Page_Down>")
+
+ tablecell[nrTablecell-1].click()
+
+ try:
+ next = cProject.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Next push button")
+
+
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ #next.doubleClick()
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.fail("Next push button not sensitive")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ text = cProject.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ if (len(text) < 1):
+ self.fail("can not find text boxes")
+
+ # am schimbat 3 cu 2 si 4 cu 3
+ text[3].click()
+ self.log.info("# Step : Click on box <<Author>>")
+ text[3].text = author
+ self.log.info("* Check : Insert Author")
+
+ text[4].click()
+ self.log.info("# Step : Click on box <<Copyright notice>>")
+ text[4].text = copyrightproject
+ self.log.info("* Check : Insert Copyrght notice")
+
+ try:
+ finish = cProject.child(name=finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = cProject.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ if (finish.sensitive):
+ self.log.info("* Check : Found Finish push button sensitive")
+ finish.doubleClick()
+ time.sleep(2)
+ self.log.info("# Step : Click <<Finish>>")
+ else:
+ self.fail("Finish push button not sensitive")
+
+
+
+ def add_new_profile(self, newprofileName):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=preferences_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+ except:
+ self.fail("can not find Preferences menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ try:
+ yoctoProjectADT = app.child(name = YP_ADT_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT button")
+
+ except:
+ self.fail("can not find Yocto Project ADT table cell")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ yoctoProjectADT.click()
+ self.log.info("# Step : Click on <<Yocto Project ADT>>")
+
+ try:
+ saveAs = preferences.child(name = saveAs_button, roleName = button_rolename)
+ self.log.info("* Check : Found Save as push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Save as push button")
+
+
+
+ saveAs.click()
+ self.log.info("# Step : Click <<Save as ...>>")
+
+ try:
+ saveAsProfile = app.child(name = saveAs_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Save as new cross development profile frame")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Save as new cross development profile frame")
+
+
+ try:
+ inputProfileName = saveAsProfile.child(roleName = text_rolename)
+ self.log.info("* Check : Found box to input Profile Name")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find text")
+
+
+ inputProfileName.click()
+ self.log.info("# Step : Click on box <<Profile Name>>")
+
+ inputProfileName.text = newprofileName
+
+ self.log.info("* Check : Insert Profile Name")
+
+ found = 1
+ try:
+ ok = saveAsProfile.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("can not find OK push button sensitive")
+
+
+
+ comboBox = preferences.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+
+ if (len(comboBox) > 0):
+ comboBox[0].click()
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+
+ try:
+ preferences.child(name=newprofileName, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found profile name menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find profile name menu item")
+
+
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find General table cell")
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ try:
+ applyPref = preferences.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ if (applyPref.sensitive):
+ applyPref.click()
+ self.log.info("# Step : Click <<Apply>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+ try:
+ ok = preferences.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+
+
+ def open_perspective(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ found = 1
+ try:
+ openPersp = app.child(name = open_perspective_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Open Associated Perspective")
+ except:
+ found = 0
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+
+ if found == 1:
+ try:
+ yes = openPersp.child(name=yes_button, roleName = button_rolename)
+ self.log.info("* Check : Found Yes push button")
+ except:
+ self.fail("can not find Yes push button")
+
+
+ if (yes.sensitive):
+ self.log.info("* Check : Found Yes push button sensitive")
+ yes.click()
+ #yes.doubleClick()
+ self.log.info("# Step : Click on button <<Yes>>")
+ else:
+ self.fail("Yes push button not sensitive")
+
+
+
+ def reconfigure_project(self,projectname):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ text[nrText-1].click()
+ self.log.info("# Step : Click on box <<Quick Access>>")
+ text[nrText-1].text='Reconfigure Project'
+ self.log.info("# Step : Insert <<Reconfigure Project>> on box <<Quick Access>>")
+
+ try:
+ reconfig = app.child(name = reconfigureproject_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found C Reconfigure Project table cell")
+ reconfig.click()
+ self.log.info("# Step : Click on box <<Reconfigure Project >>")
+ except:
+ keyCombo("<<Esc>")
+ self.log.warning("can not find Reconfigure Project table cell on Quick Access")
+ try:
+ project = eclipse.child(name = projectname, roleName=tablecell_rolename)
+ self.log.info("* Check : Found <<" +str(projectname) + ">> table cell")
+ except:
+ self.fail("can not find " +str(projectname) + " table cell")
+
+
+ project.click()
+ self.log.info("* Step : Click on project <<" +str(projectname) + ">>")
+
+ keyCombo("<<Shift><F10>")
+ time.sleep(2)
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ pressKey("Enter") # Reconfigure project
+
+ time.sleep(10)
+
+
+
+ def verify_console(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ text[nrText-1].click()
+ self.log.info("# Step : Click on box <<Quick Access>>")
+ text[nrText-1].text='Console'
+ self.log.info("# Step : Insert <<Console>> on box <<Quick Access>>")
+
+ try:
+ console = app.child(name = console_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Console")
+ except:
+ self.fail("can not find Problems table cell")
+
+ console.click()
+ self.log.info("# Step : Click on box <<Close Project>>")
+
+ text[nrText-2].click()
+ #self.log.info("# Step : Click on" + str(projectname) + " Console")
+ time.sleep(3)
+ if "error" in text[nrText-2].text:
+ self.log.info("# Step : Output from Console:")
+ self.log.info(str(text[nrText-2].text))
+ else:
+ self.fail("Error found in Console")
+
+ try:
+ clearconsole = eclipse.child(description=clear_console_description, roleName = button_rolename)
+ self.log.info("* Check : Found Clear Console push button")
+ except:
+ self.fail("can not find Clear Console push button")
+
+
+ clearconsole.click()
+ self.log.info("# Step : Click on button <<Clear Console>>")
+
+ def verify_profile(self, projectname, toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr, newprofileName):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ try:
+ project = eclipse.child(name = project_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project menu")
+ except:
+ self.fail("can not find Project menu")
+
+ project.click()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ try:
+ targetProfiles = project.child(name = targetprofiles_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project Target Profiles menu")
+ except:
+ self.fail("can not find Target Profiles menu")
+
+
+ targetProfiles.select()
+ self.log.info("# Step : Select <<Target Profiles>> menu")
+
+ # try to avoid a display bug. Need to select Target Profiles twice to see profileitem with name = newprofileName
+
+ targetProfiles.deselect()
+ self.log.info("# Step : Exit <<Target Profiles>> menu")
+
+ project.deselect()
+ self.log.info("# Step : Exit <<Project>> menu")
+
+ project.click()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ targetProfiles.select()
+ self.log.info("# Step : Select <<Target Profiles>> menu")
+
+ try:
+ profileitem = targetProfiles.child(name = newprofileName , roleName = radio_menu_item_rolename)
+ self.log.info("* Check : Found <<"+ str(newprofileName) + ">> menu item")
+ except:
+ self.fail("can not find" +str(newprofileName) + ">> menu item")
+
+
+ targetProfiles.deselect()
+ self.log.info("# Step : Exit <<Target Profiles>> menu")
+
+ project.deselect()
+ self.log.info("# Step : Exit <<Project>> menu")
+
+ try:
+ project = eclipse.child(name = project_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project menu")
+ except:
+ self.fail("can not find Project menu")
+
+
+ project.select()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ try:
+ changeYPSettings = project.child(name = changeYPsettings_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found Change Yocto Project Settings menu item")
+ except:
+ self.fail("can not find Change Yocto Project Settings menu item")
+
+
+ changeYPSettings.click()
+ self.log.info("# Step : Select <<Change Yocto Project Settings menu >> menu item")
+
+ try:
+ projectProperties = app.child(name = 'Properties for ' + str(projectname) + " ", roleName = frame_rolename)
+ self.log.info("* Check : Window Properties for " + str(projectname) + " is Open")
+ except:
+ self.fail("can not find Properties for " + str(projectname) + " frame")
+
+
+ comboBox = projectProperties.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ if str(newprofileName) in str(comboBox[0]):
+ self.log.info("* Check : Profile is saved correctly")
+ else:
+ self.log.warning("# Warning : Profile is not saved correctly saved")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+
+ try:
+ checkBox = projectProperties.findChildren(predicate.GenericPredicate(roleName=checkbox_rolename))
+ self.log.info("* Check : Found check box <<Use project specific settings>>")
+ except:
+ self.fail("can not find check box <<Use project specific settings>")
+
+ checkBox[0].click()
+ self.log.info("# Step : Click on check box <<Use project specific settings>>")
+
+ try:
+ crosscompiler = projectProperties.child(name = toolchainType, roleName = radiobutton_rolename)
+ self.log.info("* Check : Found toolchain type radio button")
+ except:
+ self.fail("can not find toolchain type radio button")
+
+ crosscompiler.click()
+ self.log.info("# Step : Click on radio button <<" + str(toolchainType) + ">>")
+
+ text = projectProperties.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ text[2].click()
+ self.log.info("# Step : Click on box <<Toolchain Root Location>>")
+ if text[2].text == toolchainAdr:
+ self.log.info("* Check : Toolchain Root Location is saved correctly")
+ else:
+ self.log.warning("# Warning : Toolchain Root Location is not saved correctly saved")
+
+
+ text[3].click()
+ self.log.info("# Step : Click on box <<Sysroot Location>>")
+ if text[3].text == sysrootAdr:
+ self.log.info("* Check : Sysroot Location is saved correctly")
+ else:
+ self.log.warning("# Warning : Sysroot Location is not saved correctly saved")
+
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ if str(targetArch) in str(comboBox[1]):
+ self.log.info("* Check : Target Arhitecture is saved correctly")
+ else:
+ self.log.warning("# Warning : Target Arhitecture is not saved correctly saved")
+
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+ try:
+ targetT = projectProperties.child(name = targetType , roleName = radiobutton_rolename)
+ self.log.info("* Check : Found target type radio button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find target type radio button")
+
+
+ targetT.click()
+ self.log.info("# Step : Click on radio button <<QEMU>>")
+
+ if (targetType == "QEMU"):
+ text[4].click()
+ self.log.info("# Step : Click on box <<Kernel>>")
+ if text[4].text == kernelAdr:
+ self.log.info("* Check : Kernel Location is saved correctly")
+ else:
+ self.log.warning("# Warning : Kernel Locationis not saved correctly saved")
+
+ try:
+ applyPref = projectProperties.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ if (applyPref.sensitive):
+ applyPref.doubleClick()
+ self.log.info("# Step : Click <<Apply>> to save Standard Profile")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+
+ try:
+ ok = projectProperties.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = projectProperties.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+
+
+ def rename_profile(self, newprofileName, renameprofile):
+
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=preferences_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+ except:
+ self.fail("can not find Preferences menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ try:
+ yoctoProjectADT = app.child(name = YP_ADT_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Yocto Project ADT table cell")
+
+
+
+ yoctoProjectADT.click()
+ self.log.info("# Step : Click on <<Yocto Project ADT>>")
+
+ comboBox = preferences.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Target Arhitecture>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+ try:
+ preferences.child(name=newprofileName, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found profile name menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find profile name menu item")
+
+
+ try:
+ Rename = preferences.child(name = rename_button, roleName = button_rolename)
+ self.log.info("* Check : Found Rename push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Rename push button")
+
+
+ Rename.click()
+ self.log.info("# Step : Click <<Rename>>")
+
+ try:
+ renameProfile = app.child(name = rename_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Rename cross development profile frame")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Rename cross development profile frame")
+
+
+ try:
+ inputrenameProfileName = renameProfile.child(roleName = text_rolename)
+ self.log.info("* Check : Found box to Rename Profile")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find text")
+
+
+ inputrenameProfileName.click()
+ self.log.info("# Step : Click on box <<Profile Name>>")
+
+ inputrenameProfileName.text = renameprofile #RenameProfile"
+
+ self.log.info("* Check : Insert Profile Name")
+
+ found = 1
+ try:
+ ok = renameProfile.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ try:
+ cancel = renameProfile.child(name=cancel_button, roleName = button_rolename)
+ self.log.info("* Check : Found Cancel push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel.sensitive):
+ cancel.click()
+ self.fail("can not find Cancel push button")
+
+
+ if (len(comboBox) > 0):
+ comboBox[0].click()
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+
+ try:
+ preferences.child(name=renameprofile, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found Rename profile menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find profile name menu item")
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find General table cell")
+
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ try:
+ applyPref = preferences.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ if (applyPref.sensitive):
+ applyPref.click()
+ self.log.info("# Step : Click <<Apply>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+ try:
+ ok = preferences.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+ try:
+ updateProfile = app.child(name = update_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Update cross development profile frame")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Update cross development profile frame")
+
+
+ try:
+ ok = updateProfile.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ cancel_close = updateProfile.child(name=cancel_button, roleName = button_rolename)
+ self.log.info("* Check : Found Cancel push button")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button sensitive")
+
+
+ def remove_yocto_profile(self, renameprofile):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=preferences_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+ except:
+ self.fail("can not find Preferences menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ cancel_close = preferences.child(name=cancel_button, roleName = button_rolename)
+
+ try:
+ yoctoProjectADT = app.child(name = YP_ADT_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found Yocto Project ADT button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Yocto Project ADT table cell")
+
+
+ yoctoProjectADT.click()
+ self.log.info("# Step : Click on <<Yocto Project ADT>>")
+
+ comboBox = preferences.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Target Arhitecture>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+ try:
+ preferences.child(name=renameprofile, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found profile name menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find profile name menu item")
+
+
+ try:
+ Remove = preferences.child(name = remove_button, roleName = button_rolename)
+ self.log.info("* Check : Found Remove push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+
+ Remove.click()
+ self.log.info("# Step : Click <<Remove>>")
+
+ try:
+ removeProfile = app.child(name = remove_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Remove cross development profile frame")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Remove cross development profile frame")
+
+
+ try:
+ ok = removeProfile.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find General table cell")
+
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ try:
+ applyPref = preferences.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ if (applyPref.sensitive):
+ applyPref.click()
+ self.log.info("# Step : Click <<Apply>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+
+ try:
+ ok = preferences.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+
+
+ try:
+ updateProfile = app.child(name = update_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Update cross development profile frame")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Update cross development profile frame")
+
+
+ try:
+ ok = updateProfile.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ cancel = updateProfile.child(name=cancel_button, roleName = button_rolename)
+ self.log.info("* Check : Found Cancel push button")
+ if (cancel.sensitive):
+ cancel.click()
+ self.fail("can not find OK push button sensitive")
+
+
+ def close_project(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+
+ #text[nrText-1].click()
+ #self.log.info("# Step : Click on box <<Quick Access>>")
+ #text[nrText-1].text='Close Project'
+ #self.log.info("# Step : Insert <<Close Project>> on box <<Quick Access>>")
+
+ try:
+ keyCombo("<<Shift><F10>") # Right Click
+ pressKey("s") # Shortcut for close Project
+ #reconfig = app.child(name = 'Close Project - Close the selected project', roleName = tablecell_rolename)
+ self.log.info("* Check : Found Close Project")
+ except:
+ self.fail("can not find Close Project table cell")
+
+
+ #reconfig.click()
+ #self.log.info("# Step : Click on box <<Close Project>>")
+
+ def delete_project(self, projectname, typeproject):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ keyCombo("<<Esc>")
+ keyCombo("<<Esc>")
+ try:
+ project = eclipse.child(name = projectname, roleName=tablecell_rolename)
+ self.log.info("* Check : Found <<" +str(projectname) + ">> table cell")
+ except:
+ self.fail("can not find " +str(projectname) + " table cell")
+
+
+ project.click()
+ self.log.info("* Step : Click on project <<" +str(projectname) + ">>")
+
+ keyCombo("<<Esc>")
+ keyCombo("<<Esc>")
+ keyCombo("<<Shift><F10>")
+ time.sleep(2)
+ keyCombo("Down")
+ keyCombo("Down")
+ pressKey("d")
+ time.sleep(2)
+ pressKey("Enter") # Delete project
+
+ try:
+ deleteresources = app.child(name=delete_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Delete Resources>> frame")
+ except:
+ self.fail("can not find Delete Resources frame")
+
+
+ time.sleep(3)
+
+ if "New Yocto Project" in typeproject:
+ pass
+ else:
+ deletecombobox = deleteresources.findChildren(predicate.GenericPredicate(roleName=checkbox_rolename))
+ deletecombobox[0].click()
+ self.log.info("# Step : Click on check box <<Delete project contents on disk>>")
+
+ try:
+ ok = deleteresources.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = deleteresources.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+
+ found = 1
+ try:
+ deleteresources = frames[2] #app.child(name=delete_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Delete Resources>> frame")
+ except:
+ found = 0
+
+ if found == 1:
+ try:
+ continuebutton = deleteresources.child(name=continue_button, roleName = button_rolename)
+ self.log.info("* Check : Found Continue push button")
+ except:
+ self.log.info("can not find Continue push button")
+
+
+ if (continuebutton.sensitive):
+ self.log.info("* Check : Found Continue push button sensitive")
+ time.sleep(2)
+ continuebutton.click()
+ self.log.info("# Step : Click on button <<Continue>>")
+ else:
+ self.fail("Continue push button not sensitive")
+
+
+ def close_perspective(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+ time.sleep(2)
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ if eclipse_version == "mars":
+
+ try:
+ perspective = window.child(name = perspective_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Perspective menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ perspective.select()
+ self.log.info("# Step : Click on menu <<Perspective>>")
+
+ try:
+ perspectiveitem = perspective.child(name=closeperspective_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Close Perspective>> menu item")
+
+ except:
+ self.fail("can not find Close Perspective menu item")
+
+ time.sleep(3)
+ perspectiveitem.click()
+ self.log.info("# Step : Click on menu item <<Close Perspective>>")
+
+ else:
+ try:
+ windowitem = window.child(name=closeperspective_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Close Perspective>> menu item")
+ except:
+ self.fail("can not find Close Perspective menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Close Perspective>>")
+
+
+ def set_network_preferences_to_direct(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=preferences_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Preferences>> menu item")
+
+ except:
+ self.fail("can not find Preferences menu item")
+
+
+ windowitem.click()
+ self.log.info("# Step : Click on menu item <<Preferences>>")
+
+ try:
+ preferences = app.child(name = preferences_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Preferences is Open")
+ except:
+ self.fail("can not find Preferences frame")
+
+ cancel_close = preferences.child(name = cancel_button, roleName = button_rolename)
+
+ text = preferences.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ time.sleep(3)
+ typeText("Network Connections")
+ time.sleep(3)
+ keyCombo("Down")
+ keyCombo("Down")
+ self.log.info("# Step : Select <<Network Connections>>")
+
+ comboBox = preferences.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) < 1):
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+ else:
+ self.log.info("* Check : Found drop-down list ")
+
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Active Provider>>")
+
+ try:
+ directitem = preferences.child(name=direct_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found Direct menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ #cancel_close.click()
+ cancel_close.doubleClick()
+ self.fail("can not find Direct menu item")
+
+
+ directitem.click()
+ self.log.info("# Step : Click on <<Direct>> menu item")
+
+ try:
+ applyPref = preferences.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ try:
+ general = app.child(name = general_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found General button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+ self.fail("can not find General table cell")
+
+
+ general.click()
+ self.log.info("# Step : Click on <<General>>")
+
+ if (applyPref.sensitive):
+ self.log.info("* Check : Found Apply push button sensitive")
+
+ applyPref.click()
+ self.log.info("# Step : Click <<Apply>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+
+ try:
+ ok = preferences.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+
+
+ def build_project(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ project = eclipse.child(name = project_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project menu")
+ except:
+ self.fail("can not find Project menu")
+
+
+ project.click()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ try:
+ build = project.child(name=buildproject_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found Build Project menu item")
+ except:
+ self.fail("can not find Build Project menu item")
+
+
+ if (build.sensitive):
+ build.click()
+ self.log.info("# Step : Click on menu item <<Build Project>>")
+ else:
+ self.fail("Build Project menu item not sensitive")
+
+
+
+ def launch_qemu_from_eclipse(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ run = eclipse.child(name = run_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run menu")
+ except:
+ self.fail("can not find Run menu")
+
+
+ run.select()
+ self.log.info("# Step : Select <<Run>> menu")
+
+ try:
+ externalTools = run.child(name = externaltools_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run External Tools menu")
+ except:
+ self.fail("can not find External Tools menu")
+
+
+ externalTools.select()
+ self.log.info("# Step : Select <<External Tools>> menu")
+
+ externalTools.deselect()
+ self.log.info("# Step : Exit <<External Tools>> menu")
+
+ run.deselect()
+ self.log.info("# Step : Exit <<Run>> menu")
+
+ try:
+ run = eclipse.child(name = run_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run menu")
+ except:
+ self.fail("can not find Run menu")
+
+
+ run.select()
+ self.log.info("# Step : Select <<Run>> menu")
+
+ try:
+ externalTools = run.child(name = externaltools_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run External Tools menu")
+ except:
+ self.fail("can not find External Tools menu")
+
+
+ externalTools.select()
+ self.log.info("# Step : Select <<External Tools>> menu")
+
+ try:
+ qemu = externalTools.child(name = qemu_item , roleName = menuitem_rolename)
+
+ self.log.info("* Check : Found <<1 qemu_i586-poky-linux>> menu item")
+ except:
+ try:
+ qemu = externalTools.child(name = qemu2_item , roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<1 qemu_i586-poky-linux>> menu item")
+ except:
+ if eclipse_version == 'mars':
+ pass
+ else:
+ self.fail("can not find qemu menu item") ###
+
+
+ #qemu.click()
+ #time.sleep(3)
+
+
+ #self.log.info("# Step : Click on menu item <<1 qemu_i586-poky-linux>>")
+ #time.sleep(2)
+
+ externalTools.deselect()
+ self.log.info("# Step : Exit <<External Tools>> menu")
+ time.sleep(3)
+
+ run.deselect()
+ self.log.info("# Step : Exit <<Run>> menu")
+
+
+ def new_connection_for_run(self,eclipse_version):
+
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ runConfig = app.child(name=runconfiguration_frame, roleName=frame_rolename)
+
+ inifile = IniParser()
+ ip = inifile.getValue("settings-eclipse.ini", "SSH", "ip")
+ conncectionId = datetime.now().strftime("-%d-%m-%Y %H:%M:%S")
+ connection = ip +' SSH ' + conncectionId
+
+ try:
+ new = runConfig.child(name = new_button, roleName = button_rolename)
+ self.log.info("* Check : Found New push button")
+ except:
+ self.fail("can not find New push button")
+
+
+ new.click()
+ self.log.info("# Step : Click on <<New...>push button")
+
+ newConnection = app.child(name=newconnection_frame, roleName = frame_rolename)
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<System type>>")
+ text[0].text = 'SSH Only'
+ self.log.info("* Check : Insert <<SSH Only>> as System type")
+
+
+ try:
+ sshOnly = newConnection.child(name = ssh_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found SSH Only table cell")
+ except:
+ self.fail("can not find SSH Only table cell")
+
+
+ sshOnly.click()
+ self.log.info("# Step : Click on box <<System type>>")
+
+ try:
+ next = newConnection.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.fail("can not find Next push button")
+
+
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ if eclipse_version == 'kepler' or eclipse_version == 'luna':
+ text[0].click()
+ else:
+ pass
+
+ keyCombo("<<Ctrl><a>")
+
+
+ self.log.info("# Step : Click on box <<Host name>>")
+ #text[0].text = ip
+ typeText(ip)
+ #text[0].text = ip
+ self.log.info("* Check : Insert ip of QEMU as hostname")
+
+
+ if eclipse_version == 'kepler' or eclipse_version == 'luna':
+ text[1].click()
+ self.log.info("# Step : Click on box <<Connection name>>")
+ text[1].text = connection
+ self.log.info("* Check : Insert Connection name")
+
+ else:
+ text[2].click()
+ self.log.info("# Step : Click on box <<Connection name>>")
+ text[2].text = connection
+ self.log.info("* Check : Insert Connection name")
+
+
+ #text[0].click()
+ #text[0].text = ip
+
+ try:
+ finish = newConnection.child(name = finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+
+ finish.click()
+ self.log.info("# Step : Click on box <<Finish>>")
+
+
+ def run_project(self , projectname, searchtype, eclipse_version):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ try:
+ run = eclipse.child(name = run_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run menu")
+ except:
+ self.fail("can not find Run menu")
+
+
+ run.select()
+ self.log.info("# Step : Click on menu <<Run>>")
+
+ try:
+ runitem = run.child(name=runconfiguration_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Run Configurations...>> menu item")
+ except:
+ self.fail("can not find Run Configurations menu item")
+
+
+ time.sleep(3)
+ runitem.click()
+ self.log.info("# Step : Click on menu item <<Run Configurations...>>")
+
+ try:
+ runConfig = app.child(name=runconfiguration_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Run Configurations>> frame")
+ except:
+ self.fail("can not find Run Configurations frame")
+
+
+ time.sleep(3)
+ typeText(str(projectname))
+
+ time.sleep(10)
+ keyCombo("Enter")
+
+
+ self.new_connection_for_run(eclipse_version)
+ self.log.info("# Step : Create a new Connection")
+
+ text = runConfig.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ if (len(text) < 6):
+ self.fail("can not find text boxes")
+
+ if eclipse_version=="mars":
+ text[4].click()
+ self.log.info("# Step : Click on box <<Remote Absolute File Path for C/C++ App>>")
+ text[4].text = '/home/root/' + str(projectname)
+ self.log.info("* Check : Insert Remote Absolute File Path for C/C++ App")
+ else:
+ text[5].click()
+ self.log.info("# Step : Click on box <<Remote Absolute File Path for C/C++ App>>")
+ text[5].text = '/home/root/' + str(projectname)
+ self.log.info("* Check : Insert Remote Absolute File Path for C/C++ App")
+
+ if "Hello World GTK C" in str(searchtype):
+ if eclipse_version=='mars':
+ text[5].click()
+ text[5].text = 'export DISPLAY=:0.0'
+ else:
+ text[6].click()
+ text[6].text = 'export DISPLAY=:0.0'
+
+ cancel_close = runConfig.child(name = close_button, roleName = button_rolename)
+
+ try:
+ run = runConfig.child(name=run_button, roleName = button_rolename)
+ self.log.info("* Check : Found Run push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Run push button")
+
+
+ if (run.sensitive):
+ self.log.info("* Check : Found Run push button sensitive")
+ run.click()
+ self.log.info("# Step : Click on button <<Run>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Run push button not sensitive")
+
+
+ found = 1
+ try:
+ enterPassword = app.child(name=enterpassword_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Enter Password>> frame")
+ except:
+ found = 0
+
+ if found == 1:
+ text = enterPassword.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ if (len(text) == 0):
+ self.fail("can not find text boxes")
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<User ID>>")
+ text[0].text = 'root'
+ self.log.info("* Check : Insert <<root>> in <<User ID>> box")
+
+ try:
+ ok = enterPassword.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = enterPassword.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+ found = 1
+ try:
+ warning = app.child(name=warning_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Warning>> frame")
+ except:
+ found = 0
+ if found == 1:
+ try:
+ yes = warning.child(name=yes_button, roleName = button_rolename)
+ self.log.info("* Check : Found Yes push button")
+ except:
+ self.fail("can not find Yes push button")
+
+
+ if (yes.sensitive):
+ self.log.info("* Check : Found Yes push button sensitive")
+ yes.click()
+ self.log.info("# Step : Click on button <<Yes>>")
+ else:
+ self.fail("Yes push button not sensitive")
+
+
+ if "Hello World GTK C" in str(searchtype):
+
+ var = subprocess.check_output("ssh root@192.168.7.2 \"ps -x | grep " + str(projectname) + "\"", shell=True)
+ expectedmessage = '/home/root/' + str(projectname)
+ if str(expectedmessage) in str(var):
+ self.log.info("* Check : Message from QEMU: " + str(expectedmessage) )
+
+ subprocess.check_output("ssh root@192.168.7.2 \"killall " + str(projectname) + "\"", shell=True)
+ self.log.info("# Step : Kill process: " + str(expectedmessage) + "")
+ else:
+ var = subprocess.check_output("ssh root@192.168.7.2 \"./" + str(projectname) + "\"", shell=True)
+ self.log.info("* Check : Message from QEMU: " + str(var))
+
+
+ def change_profile_on_selected_project(self, projectname, newprofileName):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ project = eclipse.child(name = project_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project menu")
+ except:
+ self.fail("can not find Project menu")
+
+
+ project.click()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ try:
+ changeYPSettings = project.child(name = changeYPsettings_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found Change Yocto Project Settings menu item")
+ except:
+ keyCombo("<<Esc>")
+ self.reconfigure_project(projectname)
+ try:
+ changeYPSettings = project.child(name = changeYPsettings_item, roleName = menuitem_rolename)
+ self.log.info("* Check : Found Change Yocto Project Settings menu item")
+ except:
+ self.fail("can not find Change Yocto Project Settings menu item")
+
+
+ changeYPSettings.click()
+ self.log.info("# Step : Select <<Change Yocto Project Settings menu >> menu item")
+
+ try:
+ projectProperties = app.child(name = projectproperties_frame + projectname + " ", roleName = frame_rolename)
+ self.log.info("* Check : Window Properties for " + str(projectname) + " is Open")
+ except:
+ self.fail("can not find Properties for " + str(projectname) + " frame")
+
+ cancel_close = projectProperties.child(name = cancel_button, roleName = button_rolename)
+ comboBox = projectProperties.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+
+ if (len(comboBox) > 1):
+ self.log.info("* Check : Found menu drop-down list Target Arhitecture")
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Target Arhitecture>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find combo boxes")
+
+ try:
+ projectProperties.child(name=newprofileName, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found profile name menu item")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find profile name menu item")
+
+
+ try:
+ applyPref = projectProperties.child(name = apply_button, roleName = button_rolename)
+ self.log.info("* Check : Found Apply push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Apply push button")
+
+
+ if (applyPref.sensitive):
+ applyPref.doubleClick()
+ self.log.info("# Step : Click <<Apply>> to save Standard Profile")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Apply push button not sensitive")
+
+
+ try:
+ ok = projectProperties.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("OK push button not sensitive")
+
+
+ def debug_project(self, projectname, searchtype):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+ try:
+ run = eclipse.child(name = run_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run menu")
+ except:
+ self.fail("can not find Run menu")
+
+
+ run.click()
+ self.log.info("# Step : Click on menu <<Run>>")
+
+ try:
+ debugitem = run.child(name=debugconfiguration_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Debug Configurations...>> menu item")
+ except:
+ self.fail("can not find Debug Configurations menu item")
+
+
+ debugitem.click()
+ self.log.info("# Step : Click on menu item <<Debug Configurations...>>")
+
+ try:
+ debugConfig = app.child(name=debugconfiguration_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Debug Configurations>> frame")
+ except:
+ self.fail("can not find Debug Configurations frame")
+
+
+ text = debugConfig.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+
+ if (len(text) < 1):
+ self.fail("can not find text boxes")
+
+
+ text[0].click()
+ #text[0].text = projectname
+
+ typeText(str(projectname))
+ #self.log.info("# Step : Insert <<"+ str(projectname)+">> name of project to debug")
+
+ try:
+ debug = debugConfig.child(name=debug_button, roleName = button_rolename)
+ self.log.info("* Check : Found Debug push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = debugConfig.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+ self.fail("can not find Debug push button")
+
+
+ if (debug.sensitive):
+ self.log.info("* Check : Found Debug push button sensitive")
+ debug.click()
+ self.log.info("# Step : Click on button <<Debug>>")
+ else:
+ self.log.warning("# Warning : Click <<Close>> and jump to next step")
+ cancel_close = debugConfig.child(name = close_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Debug push button not sensitive")
+
+
+ found = 1
+ try:
+ confirm = app.child(name=confirmperspectiveswitch_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Confirm Perspective Switch >> frame")
+ except:
+ found = 0
+
+ if found == 1:
+ try:
+ yes = confirm.child(name=yes_button, roleName = button_rolename)
+ self.log.info("* Check : Found Yes push button")
+ except:
+ self.fail("can not find Yes push button")
+
+
+ if (yes.sensitive):
+ self.log.info("* Check : Found Yes push button sensitive")
+ yes.doubleClick()
+ self.log.info("# Step : Click on button <<Yes>>")
+ else:
+ self.fail("Yes push button not sensitive")
+
+
+ time.sleep(20)
+
+ #var = subprocess.check_output("ssh root@192.168.7.2 \"ps -ef | grep gdbserver " + str(projectname) + "\"", shell=True)
+ #print var
+ if eclipse_version == 'mars':
+ var = subprocess.check_output("ssh root@192.168.7.2 \"killall gdbserver\"", shell=True)
+ self.log.info("# Step : Kill gdbserver open by : " + str(projectname) + "")
+ else:
+ if "Hello World GTK C" in str(searchtype):
+ var = subprocess.check_output("ssh root@192.168.7.2 \"killall gdbserver\"", shell=True)
+ self.log.info("# Step : Kill gdbserver open by : " + str(projectname) + "")
+
+
+ self.close_perspective()
+ self.log.info("# Step : Close Debug perspective")
+
+ found = 1
+ try:
+ dateforgdb = datetime.now().strftime("%m/%d/%y, %I:%M %p")
+ gdbserver = app.child(name= gdbserverdebugger_frame + dateforgdb, roleName=frame_rolename)
+ self.log.info("* Check : Found <<"+ gdbserverdebugger_frame+">> frame")
+ except:
+ found = 0
+ if found == 1:
+ try:
+ details = gdbserver.child(name=details_button, roleName = button_rolename)
+ self.log.info("* Check : Found Details push button")
+ except:
+ self.fail("can not find Details push button")
+
+
+ if (details.sensitive):
+ self.log.info("* Check : Found Details push button sensitive")
+ details.click()
+ self.log.info("# Step : Click on button <<Details>>")
+ else:
+ self.fail("Details push button not sensitive")
+
+
+ found = 1
+ try:
+ errorconnection = gdbserver.child(name= error_connection_closed, roleName=tablecell_rolename)
+ except:
+ found = 0
+ if found == 1:
+ self.log.info("* Warning : Found" + error_connection_closed +" ?????")
+ try:
+ ok = gdbserver.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = gdbserver.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+
+ def open_other_perspective(self, perspectivename):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ window = eclipse.child(name = window_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Window menu")
+ except:
+ self.fail("can not find Window menu")
+
+
+ window.click()
+ self.log.info("# Step : Click on menu <<Window>>")
+
+ try:
+ windowitem = window.child(name=openperspective_menu, roleName=menu_rolename)
+ self.log.info("* Check : Found <<Open Perspective>> menu")
+ except:
+ self.fail("can not find Open Perspective menu")
+
+
+ windowitem.select()
+ self.log.info("# Step : Select menu <<Open Perspective>>")
+
+ try:
+ otheritem = windowitem.child(name=other_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Other...>> menu item")
+
+ except:
+ self.fail("can not find Other... menu item")
+
+
+ otheritem.click()
+ self.log.info("# Step : Click on menu item <<Other...>>")
+
+ try:
+ OpenPerspective = app.child(name = openp_other_erspective_frame, roleName = frame_rolename)
+ self.log.info("* Check : Open Perspective is Open")
+ except:
+ self.fail("can not find Open Perspective frame")
+
+
+ try:
+ perspectiveitem = OpenPerspective.child(name = perspectivename, roleName=tablecell_rolename)
+ self.log.info("* Check : Found <<" +str(perspectivename) + ">> table cell")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = OpenPerspective.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find " +str(perspectivename) + " table cell")
+
+
+ perspectiveitem.click()
+ self.log.info("# Step : Click on perspective <<" + str(perspectivename) +">>")
+
+ try:
+ ok = OpenPerspective.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = OpenPerspective.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+
+ try:
+ perspectiveButton = eclipse.child(name = perspectivename, roleName = togglebutton_rolename)
+ self.log.info("* Check : Found <<" +str(perspectivename) + ">> toggle button")
+ except:
+ self.fail("can not find " +str(perspectivename) + ">> toggle button")
+
+
+ perspectiveButton.click()
+ self.log.info("# Step : Click on <<" +str(perspectivename) + ">>")
+ self.log.info("# Step : Perspective <<" +str(perspectivename) + ">> is open")
+
+ def create_yocto_project(self, projectname, projectlocation, typeproject):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ try:
+ project = eclipse.child(name = file_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found File menu")
+
+ except:
+ self.fail("can not find File menu")
+
+
+ project.click()
+ self.log.info("# Step : Click on menu <<File>>")
+
+ try:
+ newmenu = project.child(name = new_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found New menu")
+ except:
+ self.fail("can not find New menu")
+
+
+ newmenu.select()
+ self.log.info("# Step : Select <<New>> menu")
+
+ try:
+ projectitem = newmenu.child(name = project_item , roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<Project...>> menu item")
+ except:
+ self.fail("can not find Project... menu item")
+
+
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Project...>>")
+
+ try:
+ new_proj = app.child(name = new_project_frame, roleName = frame_rolename)
+ self.log.info("* Check : New Project frame")
+ except:
+ self.fail("can not find New Project frame")
+
+
+
+ text = new_proj.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ text[0].click()
+ self.log.info("# Step : Click on box <<Wizards>>")
+ text[0].text = typeproject
+ self.log.info("* Check : Insert type of project")
+
+ try:
+ next = new_proj.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = new_proj.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find Next push button")
+
+
+ time.sleep(10)
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ #next.doubleClick()
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = new_proj.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Next push button not sensitive")
+
+
+ try:
+ BBCProject = app.child(name = yoctoBBcommander_frame, roleName = frame_rolename)
+ self.log.info("* Check : Found Yocto Project Bitbake Commander frame")
+ except:
+ self.fail("can not find Yocto Project Bitbake Commanderframe")
+
+
+ text = BBCProject.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[1].click()
+ self.log.info("# Step : Click on box <<Project Name >>")
+ text[1].text = projectname
+ self.log.info("* Check : Insert name of project")
+
+ text[2].click()
+ self.log.info("# Step : Click on box <<Project Location>>")
+ text[2].text = projectlocation
+ self.log.info("* Check : Insert location of project")
+
+
+ try:
+ finish = BBCProject.child(name = finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+
+ finish.click()
+ self.log.info("# Step : Click on box <<Finish>>")
+
+ def add_bitbake_recipe(self, projectname, srcURI, description, recipelicense, recipename, pressPopulate):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+ try:
+ project = eclipse.child(name = projectname, roleName=tablecell_rolename)
+ self.log.info("* Check : Found <<" +str(projectname) + ">> table cell")
+ except:
+ self.fail("can not find " +str(projectname) + " table cell")
+
+
+ project.click()
+ self.log.info("* Step : Click on project <<" +str(projectname) + ">>")
+
+ try:
+ file = eclipse.child(name = file_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found File menu")
+ except:
+ self.fail("can not find File menu")
+
+
+ file.click()
+ self.log.info("# Step : Click on menu <<File>>")
+
+ try:
+ newmenu = file.child(name = new_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found New menu")
+ except:
+ self.fail("can not find New menu")
+
+
+ newmenu.select()
+ self.log.info("# Step : Select <<New>> menu")
+
+ try:
+ projectitem = newmenu.child(name = bitbakerecipe_item , roleName = menuitem_rolename)
+ self.log.info("* Check : Found <<BitBake Recipe>> menu item")
+ except:
+ self.fail("can not find BitBake Recipe menu item")
+
+
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Project...>>")
+
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+ bbrecipe = frames[1]
+ text = bbrecipe.findChildren(predicate.GenericPredicate(roleName = text_rolename ))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Recipe Directory>>")
+ recipe_directory = "/" + str(projectname)
+
+ if text[0].text == recipe_directory :
+ self.log.info("* Check : Recipe directory is correct")
+ else:
+ self.fail("Other recipe directory")
+
+ text[1].click()
+ self.log.info("# Step : Click on box <<SRC_URI>>")
+ text[1].text = srcURI
+ self.log.info("* Check : Insert SRC_URI")
+
+
+ text[6].click()
+ self.log.info("# Step : Click on box <<Description>>")
+ text[6].text = description
+ self.log.info("* Check : Insert Description")
+
+
+ text[7].click()
+ self.log.info("# Step : Click on box <<License>>")
+ text[7].text = recipelicense
+ self.log.info("* Check : Insert License")
+
+
+ try:
+ finish = bbrecipe.child(name = finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+
+ if pressPopulate:
+ try:
+ populate = bbrecipe.child(name = populate_button, roleName = button_rolename)
+ self.log.info("* Check : Found Populate... push button")
+ except:
+ self.fail("can not find Populate... push button")
+
+
+ populate.click()
+ self.log.info("# Step : Click on box <<Populate...>>")
+ time.sleep(20)
+
+ if text[2].text == recipename:
+ self.log.info("* Check : Recipe Name is correct")
+ else:
+ self.fail("Recipe Name is not correct")
+
+ self.log.info("# Step : Click on box SRC_URI[md5sum]")
+ text[3].click()
+ self.log.info("# Step : Text in box SRC_URI[md5sum]: %s" %(text[3].text) )
+
+ self.log.info("# Step : Click on box SRC_URI[sha255sum]")
+ text[4].click()
+ self.log.info("# Step : Text on box SRC_URI[sha255sum]: %s" %(text[4].text) )
+
+ self.log.info("# Step : Click on Lincense File CheckSum")
+ text[5].click()
+ self.log.info("# Step : Text on box SRC_URI[sha255sum]: %s" %(text[5].text) )
+ else:
+ text[2].click()
+ self.log.info("# Step : Click on box <<Recipe Name>>")
+ text[2].text = recipename
+ self.log.info("* Check : Insert Recipe Name")
+
+ finish.click()
+ self.log.info("# Step : Click on button <<Finish>>")
+
+ time.sleep(3)
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ if eclipse_version == 'mars':
+ text[nrText-1].click()
+ self.log.info("# Step : Click on " + str(projectname) + " Console")
+ time.sleep(3)
+ self.log.info("# Step : Output from " + str(projectname) + " Console: %s" %((text[nrText-1].text)))
+ else:
+ text[nrText-2].click()
+ self.log.info("# Step : Click on <<" + str(projectname) + ">> Console")
+ time.sleep(3)
+ self.log.info("# Step : Output from " + str(projectname) + " Console: %s "%(text[nrText-2].text))
+
+ try:
+ clearconsole = eclipse.child(description=clear_console_description, roleName = button_rolename)
+ self.log.info("* Check : Found Clear Console push button")
+ except:
+ self.fail("can not find Clear Console push button")
+
+
+ clearconsole.click()
+ self.log.info("# Step : Click on button <<Clear Console>>")
+
+ project.actions['expand or contract'].do()
+ self.log.info("# Step : Expand or contract")
+
+ project.grabFocus()
+ project.typeText(recipename)
+ self.log.info("# Step : Search " + str(recipename) + "")
+
+ try:
+ foundrecipe = eclipse.child(name = recipename, roleName = tablecell_rolename)
+ self.log.info("* Check : Found recipe: " + str(recipename) + " in BitBake Project <<" + str(projectname) + ">>")
+ except:
+ self.fail("can not find " + str(recipename) + "in BitBake Project <<" + str(projectname) + ">>")
+
+
+ foundrecipe.click()
+ self.log.info("# Step : Click on recipe file " + str(recipename) + "")
+
+ project.actions['expand or contract'].do()
+ self.log.info("# Step : Expand or contract")
+
+ project.click()
+ self.log.info("* Step : Click on project <<" +str(projectname) + ">>")
+
+
+ def start_HOB_or_toaster(self,eclipse_version):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ try:
+ project = eclipse.child(name = project_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Project menu")
+ except:
+ self.fail("can not find Project menu")
+
+
+ project.select()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ if eclipse_version == 'kepler':
+
+ try:
+ projectitem = project.child(name=launchHob_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Launch HOB>> menu item")
+ except:
+ project.deselect()
+ time.sleep(10)
+ project.select()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ time.sleep(3)
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Launch HOB>>")
+
+ try:
+ Launch_HOB = app.child(name = launchHob_frame, roleName = frame_rolename)
+ self.log.info("* Check : Window Launch HOB is Open")
+ except:
+ self.fail("can not find Launch HOB frame")
+
+
+ try:
+ ok = Launch_HOB.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = Launch_HOB.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+ time.sleep(10)
+
+ app2 = tree.root.application('bitbake')
+
+ try:
+ Hob = app2.child(name = 'Hob', roleName='frame')
+ self.log.info("* Check : Window %s is Open " %Hob)
+ except:
+ self.fail("FAIL: can not find %s frame " %Hob)
+
+ os.system("killall hob")
+
+
+ elif eclipse_version == 'luna':
+
+ try:
+ projectitem = project.child(name=launchToaster_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Launch Toaster>> menu item")
+ except:
+ project.deselect()
+ time.sleep(10)
+ project.select()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ time.sleep(3)
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Launch Toaster>>")
+
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+ Launch_Toaster = frames[1]
+ text = Launch_Toaster.findChildren(predicate.GenericPredicate(roleName = text_rolename ))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Toaster Server URL>")
+
+ link_toaster = 'https://www.yoctoproject.org/toaster/'
+
+ if text[0].text == link_toaster :
+ self.log.info("* Check : Toaster Server URL is correct")
+
+ else:
+ text[0].click()
+ text[0].text = link_toaster
+ try:
+ ok = Launch_Toaster.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = Launch_Toaster.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+ time.sleep(10)
+ app3 = tree.root.application('Firefox')
+
+ try:
+ Toaster = app3.child(name = 'Toaster - Mozilla Firefox', roleName='frame')
+ self.log.info("* Check : Window %s is Open " %Toaster)
+ except:
+ self.fail("FAIL: can not find %s frame " %Toaster)
+
+ os.system("killall firefox")
+
+ elif eclipse_version == 'mars':
+
+ try:
+ projectitem = project.child(name=launchToaster_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Launch Toaster>> menu item")
+ except:
+ project.deselect()
+ time.sleep(10)
+ project.select()
+ self.log.info("# Step : Click on menu <<Project>>")
+
+ time.sleep(3)
+ projectitem.click()
+ self.log.info("# Step : Click on menu item <<Launch Toaster>>")
+
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+ Launch_Toaster = frames[2]
+ text = Launch_Toaster.findChildren(predicate.GenericPredicate(roleName = text_rolename ))
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Toaster Server URL>")
+
+ link_toaster = 'https://www.yoctoproject.org/toaster/'
+
+ if text[0].text == link_toaster :
+ self.log.info("* Check : Toaster Server URL is correct")
+ else:
+ text[0].click()
+ text[0].text = link_toaster
+
+ try:
+ ok = Launch_Toaster.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = Launch_Toaster.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+ try:
+ toaster_web = eclipse.child(name='Toaster', roleName='document web')
+ self.log.info("* Check : Found <<Toaster>> document web")
+ except:
+ self.fail("can not find Toaster document web")
+
+
+
+
+ time.sleep(10)
+
+ toaster_web.click()
+ self.log.info("# Step : Click on <<Toaster>> document web")
+
+
+ def new_connection_for_debug(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ inifile = IniParser()
+ debugConfig = app.child(name=debugconfiguration_frame, roleName=frame_rolename)
+
+ ip = inifile.getValue("test.ini", "SSH", "ip")
+ conncectionId = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
+ connection = ip +'SSH_' + conncectionId
+
+ try:
+ new = debugConfig.child(name = new_button, roleName = button_rolename)
+ self.log.info("* Check : Found New push button")
+ except:
+ self.fail("can not find New push button")
+
+
+ new.click()
+ self.log.info("# Step : Click on <<New...>push button")
+
+ newConnection = app.child(name=newconnection_frame, roleName = frame_rolename)
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<System type>>")
+ text[0].text = 'SSH Only'
+ self.log.info("* Check : Insert <<SSH Only>> as System type")
+
+
+ try:
+ sshOnly = newConnection.child(name = ssh_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found SSH Only table cell")
+ except:
+ self.fail("can not find SSH Only table cell")
+
+
+ sshOnly.click()
+ self.log.info("# Step : Click on box <<System type>>")
+
+ try:
+ next = newConnection.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.fail("can not find Next push button")
+
+
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[1].click()
+ self.log.info("# Step : Click on box <<Host name>>")
+ text[1].text = ip
+ self.log.info("* Check : Insert ip of QEMU as hostname")
+
+
+
+ text[2].click()
+ self.log.info("# Step : Click on box <<Connection name>>")
+ text[2].text = connection
+ self.log.info("* Check : Insert Connection name")
+
+
+ try:
+ finish = newConnection.child(name = finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+
+ finish.click()
+ self.log.info("# Step : Click on box <<Finish>>")
+
+
+ def debug_project_to_avoid_5163(self,projectname):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ debugConfig = app.child(name=debugconfiguration_frame, roleName=frame_rolename)
+
+ text = eclipse.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+ try:
+ run = eclipse.child(name = run_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found Run menu")
+ except:
+ self.fail("can not find Run menu")
+
+
+ time.sleep(2)
+
+ run.click()
+ self.log.info("# Step : Click on menu <<Run>>")
+
+ try:
+ debugitem = run.child(name=debugconfiguration_item, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<Debug Configurations...>> menu item")
+ except:
+ self.fail("can not find Debug Configurations menu item")
+
+
+ debugitem.click()
+ self.log.info("# Step : Click on menu item <<Debug Configurations...>>")
+
+ try:
+ debugConfig = app.child(name=debugconfiguration_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Debug Configurations>> frame")
+ except:
+ self.fail("can not find Debug Configurations frame")
+
+
+ time.sleep(3)
+
+ typeText(str(projectname))
+ self.log.info("# Step : Insert <<"+ str(projectname)+">> name of project to debug")
+ time.sleep(3)
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Down")
+ keyCombo("Enter")
+
+
+ self.new_connection_for_debug()
+ self.log.info("# Step : Create a new Connection")
+
+ text = debugConfig.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ if (len(text) < 6):
+ self.fail("can not find text boxes")
+
+
+ text[5].click()
+ self.log.info("# Step : Click on box <<Remote Absolute File Path for C/C++ App>>")
+ text[5].text = '/home/root/'+ str(projectname)
+
+ self.log.info("* Check : Insert Remote Absolute File Path for C/C++ App")
+
+ try:
+ debug = debugConfig.child(name=debug_button, roleName = button_rolename)
+ self.log.info("* Check : Found Debug push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = debugConfig.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+ self.fail("can not find Debug push button")
+
+
+ if (debug.sensitive):
+ self.log.info("* Check : Found Debug push button sensitive")
+ debug.click()
+ self.log.info("# Step : Click on button <<Debug>>")
+ else:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = debugConfig.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("Debug push button not sensitive")
+
+
+ found = 1
+ try:
+ enterPassword = app.child(name=enterpassword_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Enter Password>> frame")
+ except:
+ found = 0
+
+ if found == 1:
+ text = enterPassword.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ if (len(text) == 0):
+ self.fail("can not find text boxes")
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<User ID>>")
+ text[0].text = 'root'
+ self.log.info("* Check : Insert <<root>> in <<User ID>> box")
+
+ try:
+ ok = enterPassword.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = enterPassword.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+ time.sleep(60)
+ try:
+ problem_occurred = app.child(name=problemoccurred_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Problem Occurred>> frame")
+ except:
+ self.fail("can not find Problem Occurred push button")
+
+ try:
+ ok = problem_occurred.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ time.sleep(2)
+ ok.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+
+ def connection_for_linux_tools(self,linuxToolsItem, linuxToolsFrame):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ ip = inifile.getValue("settings-eclipse.ini", "SSH", "ip")
+
+
+ try:
+ YoctoProjectTools = eclipse.child(name = yocto_project_tools_menu, roleName = menu_rolename)
+ self.log.info("* Check : Found YoctoProjectTools menu")
+ except:
+ self.fail("can not find YoctoProjectTools menu")
+
+
+ YoctoProjectTools.click()
+ self.log.info("# Step : Click on menu <<YoctoProjectTools>>")
+ time.sleep(2)
+
+ try:
+ YPTitem = YoctoProjectTools.child(name=linuxToolsItem, roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<"+linuxToolsItem+">> menu item")
+ except:
+ self.fail("can not find "+linuxToolsItem+" menu item")
+
+ YPTitem.click()
+ self.log.info("# Step : Click on menu item <<"+linuxToolsItem+">>")
+
+ try:
+ linuxtoolswindow = app.child(name=linuxToolsFrame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Perf>> frame")
+ except:
+ self.fail("can not find Debug Configurations frame")
+
+
+ try:
+ new = linuxtoolswindow.child(name = new2_button, roleName = button_rolename)
+ self.log.info("* Check : Found New push button")
+ except:
+ self.fail("can not find New push button")
+
+
+ new.click()
+ self.log.info("# Step : Click on <<New>push button")
+
+ newConnection = app.child(name=newconnection_frame, roleName = frame_rolename)
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<System type>>")
+ text[0].text = 'SSH Only'
+ self.log.info("* Check : Insert <<SSH Only>> as System type")
+
+ try:
+ sshOnly = newConnection.child(name = ssh_tablecell, roleName = tablecell_rolename)
+ self.log.info("* Check : Found SSH Only table cell")
+ except:
+ self.fail("can not find SSH Only table cell")
+
+
+ sshOnly.click()
+ self.log.info("# Step : Click on box <<System type>>")
+
+ try:
+ next = newConnection.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+ except:
+ self.fail("can not find Next push button")
+
+
+ next.click()
+ self.log.info("# Step : Click <<Next>>")
+
+ text = newConnection.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ nrText = len(text)
+ if (nrText == 0):
+ self.fail("can not find text boxes")
+
+
+
+ keyCombo("<<Ctrl><a>")
+ #text[1].click()
+ self.log.info("# Step : Click on box <<Host name>>")
+ #text[1].text = ip
+ typeText(ip)
+ self.log.info("* Check : Insert ip of QEMU as hostname")
+
+ conncectionId = datetime.now().strftime("-%d-%m-%Y %H:%M:%S")
+ connection = ip + ' ' + linuxToolsItem + conncectionId
+ if eclipse_version == 'mars':
+ text[2].click()
+ self.log.info("# Step : Click on box <<Connection name>>")
+ text[2].text = connection
+ self.log.info("* Check : Insert Connection name")
+ elif eclipse_version == 'luna':
+ text[1].click()
+ self.log.info("# Step : Click on box <<Connection name>>")
+ text[1].text = connection
+ self.log.info("* Check : Insert Connection name")
+
+ try:
+ finish = newConnection.child(name = finish_button, roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+ except:
+ self.fail("can not find Finish push button")
+
+ finish.click()
+ self.log.info("# Step : Click on box <<Finish>>")
+
+
+
+ comboBox = linuxtoolswindow.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) > 0):
+ self.log.info("* Check : Found menu drop-down list Connection")
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Connection>>")
+ else:
+ self.fail("can not find combo boxes")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = linuxtoolswindow.child(name=cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ try:
+ connectionTools = linuxtoolswindow.child(name = connection, roleName=menuitem_rolename)
+ self.log.info("* Check : Found " + str(connection) + " menu item")
+ except:
+ self.fail("can not find " + str(connection) + " menu item")
+
+
+ connectionTools.click()
+ self.log.info("# Step : Select <<" + str(connection) + ">>")
+
+ try:
+ ok_tools = linuxtoolswindow.child(name = ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+
+
+ ok_tools.click()
+ self.log.info("# Step : Click on <<OK>>push button")
+
+ found = 1
+ try:
+ enterPassword = app.child(name=enterpassword_frame, roleName=frame_rolename)
+ self.log.info("* Check : Found <<Enter Password>> frame")
+ except:
+ found = 0
+
+ if found == 1:
+ text = enterPassword.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+ if (len(text) == 0):
+ self.fail("can not find text boxes")
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<User ID>>")
+ text[0].text = 'root'
+ self.log.info("* Check : Insert <<root>> in <<User ID>> box")
+
+ try:
+ ok_enter = enterPassword.child(name=ok_button, roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+ except:
+ self.fail("can not find OK push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = enterPassword.child(name = cancel_button, roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ if (ok_enter.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ ok_enter.click()
+ self.log.info("# Step : Click on button <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+
+
+
+###################################################################
+# #
+# PART III: test cases #
+# please refer to #
+# https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=xxx
+# #
+###################################################################
+
+# Note: to comply with the unittest framework, we call these test_xxx functions
+# from run_eclipsecases.py to avoid calling setUp() and tearDown() multiple times
+
+
+class eclipse_cases(eclipse_cases_base):
+
+
+
+ ##############
+ # CASE 24 #
+ ##############
+ @testcase(24)
+ def test_24(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+ commit = inifile.getValue("settings-eclipse.ini", "Run", "commit")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ self.log.info('#############################################################################')
+ self.log.info('### TC 24 - Install Eclipse Using the Headless Build ###')
+ self.log.info('#############################################################################')
+
+ self.create_tree(eclipse_version, param1, param2, dateofrun)
+ self.log.info("# Step : Create tree for run")
+
+ self.download_basic_things(param1, param2, dateofrun)
+ self.log.info("# Step : Download basic things")
+
+ self.download_plugin_and_eclipse_version(eclipse_version, param1, param2, dateofrun)
+ self.log.info("# Step : Download plugin and eclipse version")
+
+ self.install_basic_things(param1, param2, commit, dateofrun)
+ self.log.info("# Step : Install basic things")
+
+ self.install_eclipse_version(param1, param2, eclipse_version, dateofrun)
+ self.log.info("# Step : Install eclipse %s " %(eclipse_version))
+
+ self.start_eclipse(param1, param2, eclipse_version, dateofrun)
+ self.log.info("# Step : Start Eclipse %s" %eclipse_version)
+
+ self.install_eclipse_plugin(param1, param2, eclipse_version, dateofrun)
+ self.log.info("# Step : Install Eclipse %s" %eclipse_version)
+ time.sleep(10)
+ self.restart_and_close_welcome()
+ self.log.info("# Step : Restart Eclipse %s" %eclipse_version)
+
+
+
+ @testcase(739)
+ def test_739(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Cross_Compiler_Options")
+ toolchainAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/installed/adt"
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Target_Options")
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+
+ newprofileName = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Cross_development_profiles")
+ renameprofile = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Cross_development_profiles_rename")
+ projectname = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC739_eclipse-plugin_support_profiles", "Copyright")
+
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 739 - eclipse-plugin support profiles ###")
+ self.log.info("#############################################################################")
+
+ self.work_around_preferences_frame()
+ self.log.info("# Step : Insert a text in one text box to modify size of frame")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+ self.log.info("# Step : Configure Standard Profile")
+
+ self.add_new_profile(newprofileName)
+ self.log.info("# Step : Add Profile " + str(newprofileName) + "")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ if eclipse_version == 'mars':
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.verify_profile(projectname, toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr, newprofileName)
+ self.log.info("# Step : Verify all the information about profile saved")
+
+ self.rename_profile(newprofileName, renameprofile)
+ self.log.info("# Step : Rename profile")
+
+ self.remove_yocto_profile(renameprofile)
+ self.log.info("# Step : Remove profile")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+ @testcase(25)
+ def test_25(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC25_support_SSH_connection_to_Target", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC25_support_SSH_connection_to_Target", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC25_support_SSH_connection_to_Target", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC25_support_SSH_connection_to_Target", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC25_support_SSH_connection_to_Target", "Copyright")
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 25 - Support SSH connection to Target ###")
+ self.log.info("#############################################################################")
+
+ self.set_network_preferences_to_direct()
+ self.log.info("# Step : Set connection to Direct")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.launch_qemu_from_eclipse()
+ self.log.info("# Step : Verify if QEMU appears in External Tools in Eclipse")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+
+ @testcase(26)
+ def test_26(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ toolchainAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/installed/adt"
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Cross_Compiler_Options")
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Target_Options")
+ projectname = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC26_Persistent_Yocto_Project_ADT_Settings", "Copyright")
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 26 - Persistent Yocto Project ADT settings ###")
+ self.log.info("#############################################################################")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+ self.log.info("# Step : Configure Standard Profile")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project( projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+ @testcase(27)
+ def test_27(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+
+ toolchainAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/installed/adt"
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Cross_Compiler_Options")
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Target_Options")
+
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Copyright")
+
+ newprofileName = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Cross_development_profiles_2")
+ renameprofile = inifile.getValue("settings-eclipse.ini", "TC27_Change_Yocto_Project_ADT_Settings_profile", "Cross_development_profiles_2")
+
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 27 - Change Yocto Project ADT Settings profile ###")
+ self.log.info("#############################################################################")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+ self.log.info("# Step : Configure Standard Profile")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.add_new_profile(newprofileName)
+ self.log.info("# Step : Add Profile " + str(newprofileName) )
+
+ self.change_profile_on_selected_project(projectname, newprofileName)
+ self.log.info("# Step : Change Profile")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.remove_yocto_profile(renameprofile)
+ self.log.info("# Step : Remove profile")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+ @testcase(385)
+ def test_385(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ toolchainAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/installed/adt"
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC385_ADT-installer_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_ADT", "Cross_Compiler_Options")
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC385_ADT-installer_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_ADT", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC385_ADT-installer_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_ADT", "Target_Options")
+
+
+ self.log.info("##########################################################################################################")
+ self.log.info("###TC 385 - ADT-installer - Configure Eclipse plugin with the toolchain and sysroot installed using ADT###")
+ self.log.info("##########################################################################################################")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+ self.log.info("# Step : Configure standard profile")
+
+
+
+ @testcase(38)
+ def test_38(self):
+
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("#############################################################################")
+ self.log.info("###TC 38 - ADT-installer - C - Build Hello World ANSI C Autotools Project ###")
+ self.log.info("#############################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+ @testcase(39)
+ def test_39(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 39 - ADT-installer - C++ - Build Hello World C++ Autotools Project ###")
+ self.log.info("#############################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype, eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+ @testcase(377)
+ def test_377(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+
+ self.log.info("#############################################################################")
+ self.log.info("###TC 377 - ADT-installer - C - Build Hello World GTK C Autotools Project###")
+ self.log.info("#############################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+ @testcase(386)
+ def test_386(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ toolchainAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/installed/toolchain"
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC386_Relocatable_SDK_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_Relocatable_SDK", "Cross_Compiler_Options")
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC386_Relocatable_SDK_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_Relocatable_SDK", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC386_Relocatable_SDK_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_Relocatable_SDK", "Target_Options")
+
+ self.log.info("########################################################################################################################")
+ self.log.info("###TC 386 - Relocatable SDK - Configure Eclipse plugin with the toolchain and sysroot installed using Relocatable SDK###")
+ self.log.info("########################################################################################################################")
+
+ self.work_around_preferences_frame()
+ self.log.info("# Step : Insert a text in one text box to modify size of frame")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+ self.log.info("# Step : Configure standard profile")
+
+
+ @testcase(373)
+ def test_373(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("################################################################################")
+ self.log.info("###TC 373 - Relocatable SDK - C - Build Hello World ANSI C Autotools Project ###")
+ self.log.info("################################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+ @testcase(379)
+ def test_379(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+
+ self.log.info("##############################################################################")
+ self.log.info("###TC 379 - Relocatable SDK - C++ - Build Hello World C++ Autotools Project###")
+ self.log.info("##############################################################################")
+
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+ @testcase(378)
+ def test_378(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("################################################################################")
+ self.log.info("###TC 378 - Relocatable SDK - C - Build Hello World GTK C Autotools Project###")
+ self.log.info("################################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+ @testcase(387)
+ def test_387(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+
+ poky_path = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/poky/"
+ toolchainAdr = poky_path + "build/"
+ kernelAdr = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/download/adt-installer/download_image/bzImage-qemux86.bin"
+
+ sysrootAdr = '/home/' + getpass.getuser() + '/test-yocto/qemux86'
+ toolchainType = inifile.getValue("settings-eclipse.ini", "TC387_User_Built_SDK-Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_User_Built_SDK", "Cross_Compiler_Options")
+ targetArch = inifile.getValue("settings-eclipse.ini", "TC387_User_Built_SDK-Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_User_Built_SDK", "Target_Arhitecture")
+ targetType = inifile.getValue("settings-eclipse.ini", "TC387_User_Built_SDK-Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_User_Built_SDK", "Target_Options")
+
+ #kernelAdr = poky_path +"build/tmp/deploy/images/qemux86/bzImage-qemux86.bin"
+ #sysrootAdr = poky_path + "build/tmp/sysroots/qemux86"
+ #sysrootAdr = inifile.getValue("settings-eclipse.ini", "TC387_User_Built_SDK-Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_User_Built_SDK", "Sysroot_Location")
+
+ self.log.info("########################################################################################################################")
+ self.log.info("### TC 387 - User Built SDK - Configure Eclipse plugin with the toolchain and sysroot installed using User Built SDK ###")
+ self.log.info("########################################################################################################################")
+
+ self.work_around_preferences_frame()
+ self.log.info("# Step : Insert a text in one text box to modify size of frame")
+
+ self.configure_standard_profile(toolchainType, toolchainAdr, sysrootAdr, targetArch, targetType, kernelAdr)
+
+
+
+ @testcase(374)
+ def test_374(self):
+
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 374 - User Built SDK - C - Build Hello World ANSI C Autotools Project ###")
+ self.log.info("################################################################################")
+
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+
+
+ @testcase(380)
+ def test_380(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 380 - User Built SDk - C++ - Build Hello World C++ Autotools Project ###")
+ self.log.info("################################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+ @testcase(42)
+ def test_42(self):
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Wizards")
+ searchtype = inifile.getValue("settings-eclipse.ini", "TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Search")
+ author = inifile.getValue("settings-eclipse.ini", "TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Author")
+ copyrightproject = inifile.getValue("settings-eclipse.ini", "TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project", "Copyright")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 42 - User Built SDK - C - Build Hello World GTK C Autotools Project ###")
+ self.log.info("################################################################################")
+
+ self.createProject(projectname, typeproject, searchtype, author, copyrightproject)
+ self.log.info("# Step : Create Project")
+
+ self.open_perspective()
+ self.log.info("# Step : Change Perspective")
+
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ if eclipse_version == 'mars':
+ self.reconfigure_project(projectname)
+ self.log.info("# Step : Reconfigure Project")
+
+ self.build_project()
+ self.log.info("# Step : Build Project")
+
+ self.run_project(projectname, searchtype,eclipse_version)
+ self.log.info("# Step : Run Project")
+
+
+ self.debug_project(projectname, searchtype)
+ self.log.info("# Step : Debug Project")
+
+
+ #check_passed3ewb = verify_console()
+ #ewb
+ #self.log.info("# Step : Verify problems")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close C/C++ perspective")
+
+
+ @testcase(388)
+ def test_388(self):
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ projectlocation = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun +'/download/'
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "Wizards")
+ perspectivename = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "Perspective_name")
+ srcURI = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "SRC_URI")
+ description = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "Description")
+ recipelicense = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "License")
+ recipename = inifile.getValue("settings-eclipse.ini", "TC388_BB_Commander-Create_a_User_customized_recipe", "Recipe_name")
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 388 - BB Commander - Create a User customized recipe ###")
+ self.log.info("################################################################################")
+
+ self.open_other_perspective(perspectivename)
+ self.log.info("# Step : Create BB Commander Project")
+
+ self.create_yocto_project(projectname, projectlocation, typeproject)
+ self.log.info("# Step : Create BB Commander Project")
+
+ self.add_bitbake_recipe(projectname, srcURI, description,recipelicense,recipename, pressPopulate=False)
+ self.log.info("# Step : Create BitBake Recipe")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close BitBake Commander perspective")
+
+
+
+ @testcase(153)
+ def test_153(self):
+
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ projectlocation = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + '/download/'
+
+ projectname = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "Project_name")
+ typeproject = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "Wizards")
+ perspectivename = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "Perspective_name")
+ srcURI = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "SRC_URI")
+ description = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "Description")
+ recipelicense = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "License")
+ recipename = inifile.getValue("settings-eclipse.ini", "TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package", "Recipe_name")
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 153 - BB Commander - Create a customized recipe using a source package###")
+ self.log.info("################################################################################")
+
+ self.open_other_perspective(perspectivename)
+ self.log.info("# Step : Create BB Commander Project")
+
+ self.create_yocto_project(projectname, projectlocation, typeproject)
+ self.log.info("# Step : Create BB Commander Project")
+
+ self.add_bitbake_recipe(projectname, srcURI, description, recipelicense, recipename, pressPopulate=True)
+ self.log.info("# Step : Create BitBake Recipe")
+
+ self.close_project()
+ self.log.info("# Step : Close Project")
+
+ self.delete_project(projectname, typeproject)
+ self.log.info("# Step : Delete Project")
+
+ self.close_perspective()
+ self.log.info("# Step : Close BitBake Commander perspective")
+
+
+ @testcase(159)
+ def test_159(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ poky = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + '/download/poky'
+ workspace = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/" + eclipse_version + '/workspace/'
+ kernelArch = inifile.getValue("settings-eclipse.ini", "TC159_Yocto-BSP", "Kernel_Arhitecture")
+ if (kernelArch == "qemu"):
+ qemuArch = inifile.getValue("settings-eclipse.ini", "TC159_Yocto-BSP", "Qemu_Arhitecture")
+ else:
+ qemuArch = ""
+ kernel = inifile.getValue("settings-eclipse.ini", "TC159_Yocto-BSP", "Kernel")
+ kernelBranch = inifile.getValue("settings-eclipse.ini", "TC159_Yocto-BSP", "Kernel_branch")
+
+ projectNameId = datetime.now().strftime("%d-%m-%Y-%H:%M:%S")
+ projectName = 'BSP_' + projectNameId
+
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 159 - Yocto-BSP tool ###")
+ self.log.info("################################################################################")
+
+
+ try:
+ YoctoProjectTools = eclipse.child(name = 'YoctoProjectTools', roleName = menu_rolename)
+ self.log.info("* Check : Found YoctoProjectTools menu")
+
+ except:
+ self.fail("can not find YoctoProjectTools menu")
+
+
+ YoctoProjectTools.click()
+ self.log.info("# Step : Click on menu <<YoctoProjectTools>>")
+ time.sleep(2)
+
+
+ try:
+ YPTitem = YoctoProjectTools.child(name='yocto-bsp', roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<yocto-bsp>> menu item")
+
+ except:
+ self.fail("can not find yocto-bsp menu item")
+
+
+ YPTitem.click()
+ self.log.info("# Step : Click on menu item <<yocto-bsp>>")
+
+ app = tree.root.application('Eclipse')
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+
+ yoctoBsp = frames[1]
+ text = yoctoBsp.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Metadata location>>")
+ text[0].text = poky
+ self.log.info("* Check : Insert Metadata location")
+
+
+ text[1].click()
+ self.log.info("# Step : Click on box <<Build location>>")
+ text[1].text = workspace + 'build' + projectName
+ self.log.info("* Check : Insert Build location")
+
+
+ text[2].click()
+ self.log.info("# Step : Click on box <<BSP Name>>")
+ text[2].text = projectName
+ self.log.info("* Check : Insert BSP Name")
+
+
+ text[3].click()
+ self.log.info("# Step : Click on box <<BSP output location>>")
+ text[3].text = workspace + 'output' + projectName
+ self.log.info("* Check : Insert BSP output location")
+
+
+ comboBox = yoctoBsp.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) < 4):
+ self.fail("can not find combo boxes")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+ else:
+ self.log.info("* Check : Found menu drop-down list Kernel Architecture")
+
+
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Kernel Architecture>>")
+
+ try:
+ yoctoBsp.child(name=kernelArch, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found kernel architecture name menu item")
+
+ except:
+ self.fail("can not find kernel architecture name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ comboBox[1].click()
+ self.log.info("# Step : Select <<qemu>>")
+
+ print "sleep 4"
+ time.sleep(4)
+
+ try:
+ if eclipse_version =='luna' or eclipse_version=='mars':
+ keyCombo("Down")
+
+ pressKey("Enter")
+
+ #yoctoBsp.child(name="i386", roleName='menu item').click()
+ self.log.info("* Check : Found qemu architecture name menu item")
+ self.log.info("# Step : Select Qemu Architecture <<x86_64>>")
+ except:
+ self.fail("can not find qemu architecture name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ try:
+ next = yoctoBsp.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+
+ except:
+ self.fail("can not find Next push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ time.sleep(2)
+ next.doubleClick()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.fail("Next push button not sensitive")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+ comboBox[2].click()
+ self.log.info("* Check : Found kernel name menu item")
+ try:
+ yoctoBsp.child(name = kernel, roleName=menuitem_rolename).click()
+ time.sleep(6)
+ self.log.info("# Step : Select kernel<<linux-yocto_3.19>>")
+ except:
+ self.fail("can not find kernel name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+
+ comboBox[3].click()
+ self.log.info("* Check : Found kernel branch menu item")
+
+ try:
+ yoctoBsp.child(kernelBranch, roleName=menuitem_rolename).click()
+ self.log.info("# Step : Select kernel branch <<standard/base>>")
+ except:
+ self.fail("can not find kernel name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ time.sleep(10)
+
+ try:
+ finish = yoctoBsp.child(name = 'Finish', roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+
+ except:
+ self.fail("can not find Finish push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ finish.click()
+ self.log.info("# Step : Click <<Finish>>")
+
+ newYoctoBSP = app.child(name = 'Yocto-BSP ', roleName = frame_rolename)
+ try:
+ ok = newYoctoBSP.child(name='OK', roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+
+ except:
+ self.fail("can not find OK push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = '&Cancel ', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+
+ if (os.path.exists(workspace + 'build' + projectName)):
+ self.log.info('# Step : Create ' + workspace + 'build' + projectName + "")
+ else:
+ self.log.info("error: " + workspace + 'build' + projectName + " not created")
+
+ if (os.path.exists(workspace + 'output' + projectName)):
+ self.log.info('# Step : Create ' + workspace + 'output' + projectName + "")
+ else:
+ self.log.info("error: " + workspace + 'output' + projectName + " not created")
+
+
+
+ @testcase(157)
+ def test_157(self):
+
+ app = tree.root.application('Eclipse')
+ eclipse = app.child(roleName=frame_rolename)
+
+ param1 = inifile.getValue("settings-eclipse.ini", "Run", "param1")
+ param2 = inifile.getValue("settings-eclipse.ini", "Run", "param2")
+ dateofrun = inifile.getValue("settings-eclipse.ini", "Run", "date")
+ eclipse_version= inifile.getValue("settings-eclipse.ini", "Run", "eclipse_version")
+
+ poky = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + '/download/poky'
+
+ workspace = '/home/' + getpass.getuser() + '/eclipse-run/' + "run-" + param1 + "-" + param2 + "-" + dateofrun + "/" + eclipse_version + '/workspace/'
+ kernelArch = inifile.getValue("settings-eclipse.ini", "TC157_Yocto-BSP", "Kernel_Arhitecture")
+ if (kernelArch == "qemu"):
+ qemuArch = inifile.getValue("settings-eclipse.ini", "TC157_Yocto-BSP", "Qemu_Arhitecture")
+ else:
+ qemuArch = ""
+ kernel = inifile.getValue("settings-eclipse.ini", "TC157_Yocto-BSP", "Kernel")
+ kernelBranch = inifile.getValue("settings-eclipse.ini", "TC157_Yocto-BSP", "Kernel_branch")
+
+ projectNameId = datetime.now().strftime("%d-%m-%Y-%H:%M:%S")
+ projectName = 'BSP_' + projectNameId
+
+
+ self.log.info("################################################################################")
+ self.log.info("### TC 157 - Yocto-BSP tool ###")
+ self.log.info("################################################################################")
+
+
+ try:
+ YoctoProjectTools = eclipse.child(name = 'YoctoProjectTools', roleName = menu_rolename)
+ self.log.info("* Check : Found YoctoProjectTools menu")
+ except:
+ self.fail("can not find YoctoProjectTools menu")
+
+ YoctoProjectTools.click()
+ self.log.info("# Step : Click on menu <<YoctoProjectTools>>")
+ time.sleep(2)
+
+
+ try:
+ YPTitem = YoctoProjectTools.child(name='yocto-bsp', roleName=menuitem_rolename)
+ self.log.info("* Check : Found <<yocto-bsp>> menu item")
+
+ except:
+ self.fail("can not find yocto-bsp menu item")
+
+ YPTitem.click()
+ self.log.info("# Step : Click on menu item <<yocto-bsp>>")
+
+
+ frames = app.findChildren(predicate.GenericPredicate(roleName = frame_rolename))
+ if (len(frames) == 0):
+ self.fail("can not find frames")
+
+ yoctoBsp = frames[1]
+ text = yoctoBsp.findChildren(predicate.GenericPredicate(roleName = text_rolename))
+
+ message_with_empty_metadata_location = 'Enter the required fields(with *) to create new Yocto Project BSP!'
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Metadata location>>")
+ #text[0].text = wrong_poky_path
+ self.log.info("* Check : Insert Metadata location")
+
+
+ if text[4].text == message_with_empty_metadata_location:
+ self.log.info("* Check : Found message: %s" %(message_with_empty_metadata_location))
+ else:
+ self.fail("can not find %s" %(message_with_empty_metadata_location))
+
+
+ message_with_invalid_metadata_location = ' Invalid meta data location: Make sure it exists and is a directory!'
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Metadata location>>")
+ text[0].text = "dsadasdasd"
+ self.log.info("* Check : Insert Metadata location")
+
+
+ if text[4].text == message_with_invalid_metadata_location:
+ self.log.info("* Check : Found message: %s" %(message_with_invalid_metadata_location))
+ else:
+ self.fail("can not find %s" %(message_with_invalid_metadata_location))
+
+
+ wrong_poky_path = '"/home/scripts"'
+ message_with_no_poky_in_metadata_location = ' Make sure yocto-bsp exists under %s and is executable!' %(wrong_poky_path)
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Metadata location>>")
+ text[0].text = "/home"
+ self.log.info("* Check : Insert Metadata location")
+
+ if text[4].text == message_with_no_poky_in_metadata_location:
+ self.log.info("* Check : Found message: %s" %(message_with_no_poky_in_metadata_location))
+ else:
+ self.fail("can not find %s" %(message_with_no_poky_in_metadata_location))
+
+
+ text[0].click()
+ self.log.info("# Step : Click on box <<Metadata location>>")
+ text[0].text = poky
+ self.log.info("* Check : Insert Metadata location")
+
+ text[2].click()
+ self.log.info("# Step : Click on box <<BSP Name>>")
+ text[2].text = projectName
+ self.log.info("* Check : Insert BSP Name")
+
+ comboBox = yoctoBsp.findChildren(predicate.GenericPredicate(roleName=combobox_rolename))
+ if (len(comboBox) < 4):
+ self.fail("can not find combo boxes")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+ else:
+ self.log.info("* Check : Found menu drop-down list Kernel Architecture")
+
+
+ comboBox[0].click()
+ self.log.info("# Step : Click on menu drop-down list <<Kernel Architecture>>")
+
+ try:
+ yoctoBsp.child(name=kernelArch, roleName=menuitem_rolename).click()
+ self.log.info("* Check : Found kernel architecture name menu item")
+
+ except:
+ self.fail("can not find kernel architecture name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ comboBox[1].click()
+ self.log.info("# Step : Select <<qemu>>")
+
+ print "sleep de 4"
+ time.sleep(4)
+
+ try:
+ if eclipse_version =='luna' or eclipse_version=='mars':
+ keyCombo("Down")
+
+ pressKey("Enter")
+
+ #yoctoBsp.child(name="i386", roleName='menu item').click()
+ self.log.info("* Check : Found qemu architecture name menu item")
+ self.log.info("# Step : Select Qemu Architecture <<x86_64>>")
+ except:
+ self.fail("can not find qemu architecture name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ try:
+ next = yoctoBsp.child(name = next_button, roleName = button_rolename)
+ self.log.info("* Check : Found Next push button")
+
+ except:
+ self.fail("can not find Next push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+ if (next.sensitive):
+ self.log.info("* Check : Found Next push button sensitive")
+ time.sleep(2)
+ next.doubleClick()
+ self.log.info("# Step : Click <<Next>>")
+ else:
+ self.fail("Next push button not sensitive")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+
+ comboBox[2].click()
+ self.log.info("* Check : Found kernel name menu item")
+ try:
+ yoctoBsp.child(name = kernel, roleName=menuitem_rolename).click()
+ time.sleep(6)
+ self.log.info("# Step : Select kernel<<linux-yocto_4.1>>")
+ except:
+ self.fail("can not find kernel name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ time.sleep(2)
+ cancel_close.click()
+
+ comboBox[3].click()
+ self.log.info("* Check : Found kernel branch menu item")
+
+ try:
+ yoctoBsp.child(kernelBranch, roleName=menuitem_rolename).click()
+ self.log.info("# Step : Select kernel branch <<standard/base>>")
+ except:
+ self.fail("can not find kernel name menu item")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ time.sleep(10)
+
+ try:
+ finish = yoctoBsp.child(name = 'Finish', roleName = button_rolename)
+ self.log.info("* Check : Found Finish push button")
+
+ except:
+ self.fail("can not find Finish push button")
+ self.log.warning("# Warning : Click <<Cancel>> and jump to next step")
+ cancel_close = yoctoBsp.child(name = 'Cancel', roleName = button_rolename)
+ if (cancel_close.sensitive):
+ cancel_close.click()
+
+ finish.click()
+ self.log.info("# Step : Click <<Finish>>")
+
+ newYoctoBSP = app.child(name = 'Yocto-BSP ', roleName = frame_rolename)
+ try:
+ ok = newYoctoBSP.child(name='OK', roleName = button_rolename)
+ self.log.info("* Check : Found OK push button")
+
+ except:
+ self.fail("can not find OK push button")
+
+ if (ok.sensitive):
+ self.log.info("* Check : Found OK push button sensitive")
+ ok.click()
+ self.log.info("# Step : Click <<OK>>")
+ else:
+ self.fail("OK push button not sensitive")
+
+ if (os.path.exists(poky + '/meta-' + projectName)):
+ self.log.info('# Step : Create ' + poky + '/meta-' + projectName + "")
+ else:
+ self.fail("error: " + poky + 'meta-' + projectName + " not created")
+
+ @testcase(31)
+ def test_31(self):
+
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 31 - Linux tools - Perf ###")
+ self.log.info("#############################################################################")
+
+ linuxToolsItem = perf_item
+ linuxToolsFrame = perf_frame
+
+ self.connection_for_linux_tools(linuxToolsItem, linuxToolsFrame)
+ self.log.info("# Step : New SSH connection for %s" %(linuxToolsItem))
+
+ keyCombo("Enter")
+ time.sleep(4)
+
+ typeText("perf top")
+ time.sleep(4)
+ keyCombo("Enter")
+
+ @testcase(32)
+ def test_32(self):
+
+ linuxToolsItem = powertop_item
+ linuxToolsFrame = powertop_frame
+
+
+ self.log.info("#############################################################################")
+ self.log.info("### TC 32 - Linux tools - PowerTop ###")
+ self.log.info("#############################################################################")
+
+ self.connection_for_linux_tools(linuxToolsItem, linuxToolsFrame)
+ self.log.info("# Step : New SSH connection for" +linuxToolsItem)
+
+
+
+
+
diff --git a/examples/iniparser.py b/examples/iniparser.py
new file mode 100755
index 00000000000..9b0cf2360d4
--- /dev/null
+++ b/examples/iniparser.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+import os
+import sys
+import time
+
+class IniParser():
+
+ def getValue(self, inifile, section, var):
+ if os.path.isfile(inifile):
+ with open(inifile, "r") as cfgfile:
+ content = cfgfile.read()
+ content = content.split("\n")
+ for i in xrange (len(content)-1):
+ if "["+section+"]" in content[i]:
+ j = i+1
+ while not "[" in content[j]:
+ if var == content[j].split("=")[0]:
+ return content[j].split("=")[1].replace('"','').strip()
+ else:
+ j += 1
+ cfgfile.close()
+
+ def setValue(self, inifile, section, var, value):
+ if os.path.isfile(inifile):
+ with open(inifile, "r") as cfgfile:
+ content = cfgfile.read()
+ content = content.split("\n")
+ for i in xrange (len(content)-1):
+ if "["+section+"]" in content[i]:
+ j = i+1
+ while not "[" in content[j]:
+ if var == content[j].split("=")[0]:
+ content[j] = var + '="' + value +'"'
+ break
+ else:
+ j += 1
+ cfgfile.close()
+ with open(inifile, "w") as cfgfile:
+ cfgfile.write("\n".join(content))
+
+#inifile = IniParser()
+
+#print inifile.getValue("test.ini", "section3", "var3")
+ \ No newline at end of file
diff --git a/examples/results-run_eclipsetests.py.log b/examples/results-run_eclipsetests.py.log
new file mode 120000
index 00000000000..7b6349db461
--- /dev/null
+++ b/examples/results-run_eclipsetests.py.log
@@ -0,0 +1 @@
+/home/valentin/eclipse-results/releases/yocto-2.0.1.rc6-2016-02-15/results-eclipse-luna.log \ No newline at end of file
diff --git a/examples/run_eclipsetests.py b/examples/run_eclipsetests.py
new file mode 100755
index 00000000000..512cc52de72
--- /dev/null
+++ b/examples/run_eclipsetests.py
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+
+# Copyright
+
+# DESCRIPTION
+# This is script for running all selected eclipse cases on
+# selected web browsers manifested in toaster_test.cfg.
+
+
+import unittest, time, sys, os, logging, platform
+import ConfigParser
+import subprocess
+import argparse
+from eclipse_automation_test import eclipse_cases
+
+def get_args_parser():
+ description = "Script that runs eclipse auto tests."
+ parser = argparse.ArgumentParser(description=description)
+ parser.add_argument('--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False, help='Run all (unhidden) tests')
+ parser.add_argument('--list-tests', required=False, action="store_true", dest="list_tests", default=False,
+ help='List all available tests.')
+ parser.add_argument('--run-suite', required=False, dest='run_suite', default=False,
+ help='run suite (defined in ini file)')
+ return parser
+
+
+def get_tests():
+
+ testslist = []
+ prefix = 'eclipse_automation_test.eclipse_cases'
+
+ for t in dir(eclipse_cases):
+ if t.startswith('test_'):
+ testslist.append('.'.join((prefix, t)))
+
+ return testslist
+
+
+def get_tests_from_ini(suite=None):
+
+ testslist = []
+ config = ConfigParser.SafeConfigParser()
+ config.read('settings-eclipse.ini')
+
+ if suite is not None:
+ target_suite = suite.lower()
+
+ # TODO: if suite is valid suite
+
+ else:
+ target_suite = platform.system().lower()
+
+ try:
+ tests_from_ini = eval(config.get('eclipse_test_' + target_suite, 'test_cases'))
+ except:
+ print 'Failed to get test cases from ini file. Make sure the format is correct.'
+ return None
+
+ prefix = 'eclipse_automation_test.eclipse_cases.test_'
+ for t in tests_from_ini:
+ testslist.append(prefix + str(t))
+
+ return testslist
+
+def main():
+
+# In case this script is called from other directory
+ os.chdir(os.path.abspath(sys.path[0]))
+
+ parser = get_args_parser()
+ args = parser.parse_args()
+
+
+ if args.run_all_tests:
+ testslist = get_tests()
+ elif args.run_suite:
+ testslist = get_tests_from_ini(args.run_suite)
+ os.environ['ECLIPSE_SUITE'] = args.run_suite
+ else:
+ testslist = get_tests_from_ini()
+
+ if not testslist:
+ print 'Failed to get test cases.'
+ exit(1)
+
+ suite = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ loader.sortTestMethodsUsing = None
+ runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
+
+ for test in testslist:
+ try:
+ suite.addTests(loader.loadTestsFromName(test))
+ except AttributeError as e:
+ return 1
+
+ result = runner.run(suite)
+
+ if result.wasSuccessful():
+ return 0
+ else:
+ return 1
+
+
+def buildResultClass(args):
+ """Build a Result Class to use in the testcase execution"""
+
+ class StampedResult(unittest.TextTestResult):
+ """
+ Custom TestResult that prints the time when a test starts. As toaster-auto
+ can take a long time (ie a few hours) to run, timestamps help us understand
+ what tests are taking a long time to execute.
+ """
+ def startTest(self, test):
+ import time
+ self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
+ super(StampedResult, self).startTest(test)
+
+ return StampedResult
+
+
+if __name__ == "__main__":
+
+
+ try:
+ ret = main()
+ except:
+ ret = 1
+ import traceback
+ traceback.print_exc(5)
+ finally:
+ if os.getenv('ECLIPSE_SUITE'):
+ del os.environ['ECLIPSE_SUITE']
+ sys.exit(ret)
+
+
diff --git a/examples/settings-eclipse.ini b/examples/settings-eclipse.ini
new file mode 100755
index 00000000000..c5c1474d440
--- /dev/null
+++ b/examples/settings-eclipse.ini
@@ -0,0 +1,181 @@
+[eclipse_test_all]
+test_cases = [24, 31, 32]
+##24, 739, 25, 26, 27, 385, 38, 39, 377, 386, 373, 379, 378, 387, 374, 380, 42, 388, 153, 159, 157, 31, 32]
+
+[Run]
+param1="releases"
+param2="yocto-2.0.1.rc6"
+eclipse_version="luna"
+date="2016-02-15"
+commit="5b12268f6e17574999f91628a60e21711cf62ee4"
+
+[TC739_eclipse-plugin_support_profiles]
+Cross_development_profiles="TestProfile"
+Cross_Compiler_Options="Standalone pre-built toolchain"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+Project_name="ProfilePrj"
+Wizards="C Project"
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+Cross_development_profiles_rename="RenameProfile"
+
+[TC25_support_SSH_connection_to_Target]
+ip="192.168.7.2"
+Project_name="cProjecSSH"
+Wizards="C Project "
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[SSH]
+ip="192.168.7.2"
+
+[TC26_Persistent_Yocto_Project_ADT_Settings]
+Cross_Compiler_Options="Standalone pre-built toolchain"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+Project_name="Persistent"
+Wizards="C++ Project"
+Search="Hello World C++ Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC27_Change_Yocto_Project_ADT_Settings_profile]
+Cross_development_profiles="Standard Profile"
+Cross_development_profiles_2="Another Profile"
+Cross_Compiler_Options="Standalone pre-built toolchain"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+Project_name="ChangeProf"
+Wizards="C Project"
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC385_ADT-installer_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_ADT]
+Cross_development_profiles="Standard Profile"
+Cross_Compiler_Options="Standalone pre-built toolchain"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+
+[TC38_ADT-installer_C_Build_Hello_World_ANSI_C_Autotools_Project]
+Project_name="CHWProjADT"
+Wizards="C Project"
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC377_ADT-installer_C_Build_Hello_World_GTK_C_Autotools_Project]
+Project_name="cGTKPrjADT"
+Wizards="C Project"
+Search="Hello World GTK C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC39_ADT-installer_C++_Build_Hello_World_C++_Autotools project]
+Project_name="cppProjADT"
+Wizards="C++ Project"
+Search="Hello World C++ Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC386_Relocatable_SDK_Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_Relocatable_SDK]
+Cross_development_profiles="Standard Profile"
+Cross_Compiler_Options="Standalone pre-built toolchain"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+
+[TC373_Relocatable_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project]
+Project_name="CHWProjTCH"
+Wizards="C Project"
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC379_Relocatable_SDK_C++_Build_Hello_World_C++_Autotools_Project]
+Project_name="cppProjTCH"
+Wizards="C++ Project"
+Search="Hello World C++ Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC378_Relocatable_SDK_C_Build_Hello_World_GTK_C_Autotools_Project]
+Project_name="cGTKPrjTCH"
+Wizards="C Project"
+Search="Hello World GTK C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC387_User_Built_SDK-Configure_Eclipse_plugin_with_the_toolchain_and_sysroot_installed_using_User_Built_SDK]
+Cross_development_profiles="Standard Profile"
+Cross_Compiler_Options="Build system derived toolchain"
+Sysroot_Location="/home/valentin/test-yocto/qemux86"
+Target_Arhitecture="i586-poky-linux"
+Target_Options="QEMU"
+
+[TC374_User_Built_SDK_C_Build_Hello_World_ANSI_C_Autotools_Project]
+Project_name="CHWProjSDK"
+Wizards="C Project"
+Search="Hello World ANSI C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC380_User_Built_SDK_C++_Build_Hello_World_C++_Autotools_Project]
+Project_name="cppProjSDK"
+Wizards="C++ Project"
+Search="Hello World C++ Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC42_User_Built_SDK_C_Build_Hello_World_GTK_C_Autotools_Project]
+Project_name="cGTKPrjSDK"
+Wizards="C Project"
+Search="Hello World GTK C Autotools Project"
+Author="Valentin Hangan"
+Copyright="Yocto"
+
+[TC388_BB_Commander-Create_a_User_customized_recipe]
+Wizards="New Yocto Project"
+Project_name="poky2"
+Perspective_name="BitBake Commander"
+SRC_URI="ftp://ftp.gnu.org/gnu/m4/m4-1.4.9.tar.gz"
+Description="Description"
+License="Yocto"
+Recipe_name="Recipe.bb"
+
+[TC153_BB_Commander-Create_a_customized_recipeu_using_a_source_package]
+Wizards="New Yocto Project"
+Project_name="poky2"
+
+Perspective_name="BitBake Commander"
+SRC_URI="ftp://ftp.gnu.org/gnu/m4/m4-1.4.9.tar.gz"
+Description="Description"
+License="Yocto"
+Recipe_name="m4_1.4.9.bb"
+
+[TC145_BB_Commander-Launch_HOB_or_Toaster_in_Eclipse]
+Wizards="New Yocto Project"
+Project_name="poky2"
+Project_location="/home/valentin/yocto/"
+Perspective_name="BitBake Commander"
+SRC_URI="ftp://ftp.gnu.org/gnu/m4/m4-1.4.9.tar.gz"
+Description="Description"
+License="Yocto"
+Recipe_name="Hob-Toaster.bb"
+
+[TC157_Yocto-BSP]
+Kernel_Arhitecture="qemu"
+Qemu_Arhitecture="x86_64"
+Kernel="linux-yocto_4.1"
+Kernel_branch="standard/base"
+
+[TC159_Yocto-BSP]
+Kernel_Arhitecture="qemu"
+Qemu_Arhitecture="x86_64"
+Kernel="linux-yocto_4.1"
+Kernel_branch="standard/base"
+
+
+
diff --git a/icons/dogtail-head-48.png b/icons/dogtail-head-48.png
new file mode 100644
index 00000000000..630d8d5fa5c
--- /dev/null
+++ b/icons/dogtail-head-48.png
Binary files differ
diff --git a/icons/dogtail-head.svg b/icons/dogtail-head.svg
new file mode 100644
index 00000000000..4954ac833b6
--- /dev/null
+++ b/icons/dogtail-head.svg
@@ -0,0 +1,595 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
+ xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="100.00000pt"
+ height="100.00000pt"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.42"
+ sodipodi:docbase="/home/zack/Desktop/dtlogos"
+ sodipodi:docname="dogtail-head.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="XMLID_63_"
+ gradientUnits="userSpaceOnUse"
+ x1="41.803699"
+ y1="23.916500"
+ x2="1680.2037"
+ y2="23.916500"
+ gradientTransform="matrix(2.050000e-2,2.500000e-3,-6.000000e-4,4.700000e-3,21.16370,234.0289)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop2502" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop2504" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop2506" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(2.350000e-2,2.900000e-3,-7.000000e-4,5.400000e-3,21.13940,229.5353)"
+ y2="-38513.504"
+ x2="1248.4313"
+ y1="-38513.504"
+ x1="-389.96881"
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_64_">
+ <stop
+ id="stop2515"
+ style="stop-color:#4A4A4A"
+ offset="0.0275" />
+ <stop
+ id="stop2517"
+ style="stop-color:#FFFFFF"
+ offset="0.5412" />
+ <stop
+ id="stop2519"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#4A4A4A"
+ offset="0.0275" />
+ <a:midPointStop
+ style="stop-color:#4A4A4A"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5412" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <radialGradient
+ id="XMLID_96_"
+ cx="1323.0430"
+ cy="-5313.3013"
+ r="819.20001"
+ fx="1323.0430"
+ fy="-5313.3013"
+ gradientTransform="matrix(6.330000e-2,-1.680000e-2,8.500000e-3,3.210000e-2,10.88700,202.9904)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#008837"
+ id="stop2967" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop2969" />
+ <a:midPointStop
+ offset="0"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </radialGradient>
+ <linearGradient
+ gradientTransform="matrix(3.960000e-2,-1.970000e-2,1.890000e-2,3.780000e-2,36.87870,242.5071)"
+ y2="-3377.9888"
+ x2="3651.6226"
+ y1="-3377.9888"
+ x1="2013.2227"
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_59_">
+ <stop
+ id="stop2456"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop2458"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(3.960000e-2,-1.970000e-2,1.890000e-2,3.780000e-2,36.87870,242.5071)"
+ y2="-2710.3794"
+ x2="3651.6226"
+ y1="-2710.3794"
+ x1="2013.2227"
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_60_">
+ <stop
+ id="stop2463"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop2465"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(6.970000e-2,0.000000,0.000000,2.740000e-2,47.07010,253.6812)"
+ y2="-7203.4263"
+ x2="1309.2438"
+ y1="-7203.4263"
+ x1="-329.15619"
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_65_">
+ <stop
+ id="stop2534"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop2536"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(8.500000e-3,-1.880000e-2,1.330000e-2,6.000000e-3,-5.852400,270.4716)"
+ y2="-2914.9419"
+ x2="10760.650"
+ y1="-2914.9419"
+ x1="9122.2500"
+ gradientUnits="userSpaceOnUse"
+ id="XMLID_98_">
+ <stop
+ id="stop2989"
+ style="stop-color:#000000"
+ offset="0.0275" />
+ <stop
+ id="stop2991"
+ style="stop-color:#979797"
+ offset="0.5412" />
+ <stop
+ id="stop2993"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="0.0275" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#979797"
+ offset="0.5412" />
+ <a:midPointStop
+ style="stop-color:#979797"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.2187708"
+ inkscape:cx="135.29150"
+ inkscape:cy="69.758353"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="753"
+ inkscape:window-height="513"
+ inkscape:window-x="242"
+ inkscape:window-y="109" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Máirín Duffy</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Red Hat, Inc.</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:title>dogtail head</dc:title>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g1409"
+ inkscape:label="Layer 1"
+ transform="translate(-291.4285,-146.5714)">
+ <g
+ inkscape:export-ydpi="104.78611"
+ inkscape:export-xdpi="104.78611"
+ inkscape:export-filename="/home/zack/Desktop/dogtail-head-logo-128.png"
+ id="g4246"
+ transform="translate(292.8166,152.2012)">
+ <g
+ id="g4195">
+ <g
+ id="g2388">
+ <path
+ i:knockout="Off"
+ d="M 58.500000,8.7080000 C 58.500000,8.7080000 58.500000,8.7080000 58.300000,8.8080000 C 57.800000,8.5080000 57.300000,8.4080000 56.500000,8.2080000 C 56.400000,8.1080000 56.300000,8.1080000 56.100000,8.1080000 C 55.500000,7.6080000 55.000000,7.4080000 54.300000,7.4080000 C 54.200000,7.4080000 54.000000,7.4080000 54.000000,7.5080000 C 53.800000,7.5080000 53.800000,7.5080000 53.800000,7.6080000 L 53.800000,7.6080000 L 53.800000,7.8080000 C 54.000000,7.9080000 54.000000,7.9080000 54.000000,7.9080000 C 54.100000,8.0080000 54.300000,8.0080000 54.300000,8.0080000 L 54.300000,8.1080000 C 54.500000,9.9080000 54.700000,11.708000 55.000000,13.508000 C 55.200000,15.208000 55.300000,17.008000 55.500000,18.708000 C 55.500000,19.608000 55.500000,20.308000 55.700000,21.008000 C 56.100000,22.908000 56.300000,24.608000 56.400000,26.508000 C 56.600000,28.208000 56.600000,30.108000 56.400000,32.008000 C 56.300000,33.708000 56.300000,35.608000 56.300000,37.308000 C 56.300000,38.208000 56.300000,39.008000 56.300000,40.008000 C 56.300000,41.808000 56.500000,43.608000 56.800000,45.408000 C 57.000000,47.108000 57.400000,49.008000 57.700000,50.808000 C 57.800000,51.208000 58.000000,51.608000 58.300000,51.908000 C 58.300000,52.008000 58.500000,52.008000 58.500000,52.108000 C 58.600000,52.008000 58.600000,52.008000 58.700000,52.008000 C 58.700000,52.008000 58.800000,52.008000 58.900000,51.908000 C 59.500000,51.208000 60.200000,50.908000 61.100000,50.608000 C 61.500000,50.508000 62.000000,50.308000 62.300000,50.008000 C 62.500000,49.908000 62.700000,49.708000 62.700000,49.708000 C 62.700000,49.608000 62.700000,49.608000 62.800000,49.608000 L 62.800000,49.508000 C 62.700000,49.208000 62.700000,49.008000 62.600000,48.908000 C 62.000000,47.008000 61.500000,45.408000 61.100000,43.608000 C 60.700000,41.808000 60.300000,40.008000 60.100000,38.208000 C 60.000000,36.508000 59.900000,34.608000 60.000000,32.808000 C 60.000000,31.008000 60.000000,29.108000 60.000000,27.408000 C 60.000000,25.608000 60.000000,23.708000 59.900000,21.908000 C 59.800000,20.108000 59.700000,18.208000 59.500000,16.508000 C 59.300000,14.708000 59.000000,12.908000 59.000000,11.008000 C 59.000000,10.608000 59.200000,10.108000 59.300000,9.7080000 C 59.300000,9.2080000 59.000000,8.9080000 58.700000,8.7080000 C 58.600000,8.7080000 58.600000,8.7080000 58.500000,8.7080000 z M 58.200000,10.408000 L 58.300000,10.508000 C 58.200000,11.108000 58.200000,11.908000 58.300000,12.608000 C 58.300000,14.408000 58.300000,16.108000 58.500000,17.908000 C 58.700000,19.608000 58.800000,21.508000 58.900000,23.208000 C 59.000000,25.108000 59.000000,27.008000 59.000000,28.708000 C 59.000000,30.608000 59.000000,32.408000 59.000000,34.208000 C 59.000000,36.008000 59.000000,37.808000 59.300000,39.608000 C 59.500000,41.408000 59.800000,43.208000 60.300000,45.008000 C 60.500000,45.608000 60.700000,46.408000 60.800000,47.008000 C 61.000000,47.808000 61.300000,48.508000 61.900000,49.208000 L 61.800000,49.208000 C 61.800000,49.208000 61.800000,49.208000 61.800000,49.308000 C 61.800000,49.408000 61.800000,49.408000 61.800000,49.508000 C 60.900000,49.708000 60.000000,50.108000 59.300000,50.608000 C 59.200000,50.708000 59.000000,50.908000 58.800000,51.008000 C 58.800000,50.908000 58.800000,50.608000 58.700000,50.508000 C 58.700000,49.908000 58.500000,49.308000 58.300000,48.708000 C 58.000000,47.008000 57.700000,45.108000 57.500000,43.408000 C 57.400000,42.008000 57.300000,40.708000 57.300000,39.408000 C 57.200000,38.208000 57.000000,37.108000 57.200000,36.008000 C 57.300000,34.108000 57.300000,32.308000 57.400000,30.508000 C 57.500000,28.708000 57.400000,26.908000 57.200000,25.008000 C 56.900000,23.208000 56.700000,21.508000 56.300000,19.608000 C 56.300000,19.208000 56.200000,18.708000 56.100000,18.108000 C 55.800000,16.408000 55.600000,14.608000 55.500000,12.808000 C 55.400000,11.408000 55.200000,10.008000 55.000000,8.6080000 C 55.200000,8.8080000 55.400000,8.9080000 55.700000,8.8080000 C 55.800000,8.9080000 55.800000,8.9080000 55.800000,8.9080000 C 56.100000,9.0080000 56.300000,9.0080000 56.500000,9.1080000 C 56.600000,9.2080000 56.700000,9.4080000 56.700000,9.5080000 C 57.000000,9.8080000 57.400000,10.008000 58.000000,10.108000 C 58.000000,10.308000 58.100000,10.408000 58.200000,10.408000 z "
+ id="path2390" />
+ <path
+ i:knockout="Off"
+ d="M 58.300000,10.508000 L 58.200000,10.408000 C 58.100000,10.408000 58.000000,10.308000 58.000000,10.108000 C 57.400000,10.008000 57.000000,9.8080000 56.700000,9.5080000 C 56.700000,9.4080000 56.600000,9.2080000 56.500000,9.1080000 C 56.300000,9.0080000 56.100000,9.0080000 55.800000,8.9080000 C 55.800000,8.9080000 55.800000,8.9080000 55.700000,8.8080000 C 55.400000,8.9080000 55.200000,8.8080000 55.000000,8.6080000 C 55.200000,10.008000 55.400000,11.408000 55.500000,12.808000 C 55.600000,14.608000 55.800000,16.408000 56.100000,18.108000 C 56.200000,18.708000 56.300000,19.208000 56.300000,19.608000 C 56.700000,21.508000 56.900000,23.208000 57.200000,25.008000 C 57.400000,26.908000 57.500000,28.708000 57.400000,30.508000 C 57.300000,32.308000 57.300000,34.108000 57.200000,36.008000 C 57.000000,37.108000 57.200000,38.208000 57.300000,39.408000 C 57.300000,40.708000 57.400000,42.008000 57.500000,43.408000 C 57.700000,45.108000 58.000000,47.008000 58.300000,48.708000 C 58.500000,49.308000 58.700000,49.908000 58.700000,50.508000 C 58.800000,50.608000 58.800000,50.908000 58.800000,51.008000 C 59.000000,50.908000 59.200000,50.708000 59.300000,50.608000 C 60.000000,50.108000 60.900000,49.708000 61.800000,49.508000 C 61.800000,49.408000 61.800000,49.408000 61.800000,49.308000 C 61.800000,49.208000 61.800000,49.208000 61.800000,49.208000 L 61.900000,49.208000 C 61.300000,48.508000 61.000000,47.808000 60.800000,47.008000 C 60.700000,46.408000 60.500000,45.608000 60.300000,45.008000 C 59.800000,43.208000 59.500000,41.408000 59.300000,39.608000 C 59.000000,37.808000 59.000000,36.008000 59.000000,34.208000 C 59.000000,32.408000 59.000000,30.608000 59.000000,28.708000 C 59.000000,27.008000 59.000000,25.108000 58.900000,23.208000 C 58.800000,21.508000 58.700000,19.608000 58.500000,17.908000 C 58.300000,16.108000 58.300000,14.408000 58.300000,12.608000 C 58.200000,11.908000 58.200000,11.108000 58.300000,10.508000 z "
+ id="path2392"
+ style="fill:#333333" />
+ </g>
+ <g
+ id="g2497">
+ <path
+ i:knockout="Off"
+ d="M 69.700000,24.708000 C 69.600000,24.608000 69.500000,24.608000 69.200000,24.408000 C 68.800000,24.208000 68.000000,24.008000 66.700000,23.608000 C 65.400000,23.208000 62.800000,23.308000 59.000000,23.908000 C 58.800000,24.508000 58.800000,25.208000 59.200000,26.008000 C 59.600000,25.908000 60.200000,25.708000 60.700000,25.708000 C 61.200000,25.608000 61.500000,25.608000 61.800000,25.608000 C 62.000000,25.608000 62.300000,25.608000 62.500000,25.608000 C 62.800000,25.608000 63.300000,25.608000 63.900000,25.608000 C 64.500000,25.608000 65.200000,25.808000 65.700000,26.308000 C 65.700000,26.408000 65.500000,26.508000 65.200000,26.708000 C 64.700000,27.008000 64.000000,27.308000 63.000000,27.608000 C 62.000000,27.908000 60.900000,28.108000 59.700000,28.208000 C 58.300000,28.408000 56.700000,28.408000 54.700000,28.408000 C 52.600000,28.408000 51.500000,28.408000 51.300000,28.108000 C 51.100000,28.008000 51.200000,27.708000 51.700000,27.608000 C 52.100000,27.408000 53.300000,27.008000 55.300000,26.508000 C 56.300000,26.208000 56.800000,26.108000 57.000000,26.208000 C 57.200000,25.808000 57.300000,25.308000 57.100000,24.708000 C 56.900000,24.208000 57.000000,24.008000 57.100000,23.908000 C 54.300000,24.408000 52.700000,24.708000 52.300000,24.908000 C 52.100000,25.008000 51.700000,25.108000 51.000000,25.408000 C 50.400000,25.508000 49.800000,25.708000 49.200000,25.908000 C 48.500000,26.108000 48.000000,26.508000 47.500000,27.008000 C 47.100000,27.408000 46.900000,27.708000 46.900000,28.008000 C 46.800000,28.408000 47.000000,28.608000 47.000000,29.008000 C 47.000000,29.208000 47.300000,29.608000 47.700000,30.008000 C 48.000000,30.308000 48.500000,30.508000 49.200000,30.608000 C 52.100000,31.108000 55.500000,31.108000 59.200000,30.708000 C 62.800000,30.408000 65.500000,29.708000 67.200000,29.008000 L 68.300000,28.408000 C 69.200000,28.008000 69.600000,27.608000 69.700000,27.508000 C 70.000000,27.008000 70.300000,26.608000 70.300000,26.408000 C 70.400000,26.008000 70.300000,25.708000 70.200000,25.408000 C 69.900000,25.008000 69.800000,24.808000 69.700000,24.708000 z M 69.000000,25.108000 C 69.300000,25.308000 69.300000,25.408000 69.500000,25.608000 C 69.500000,26.008000 69.400000,26.408000 69.300000,26.508000 C 69.100000,26.908000 68.800000,27.008000 68.500000,27.208000 C 67.700000,27.708000 67.000000,28.008000 66.200000,28.308000 L 64.800000,28.708000 C 64.400000,28.908000 63.800000,29.108000 63.200000,29.308000 C 62.600000,29.508000 62.000000,29.608000 61.200000,29.708000 L 58.900000,30.008000 C 58.000000,30.108000 57.300000,30.108000 57.000000,30.108000 C 56.700000,30.108000 56.500000,30.108000 56.500000,30.108000 C 54.300000,30.108000 52.800000,30.108000 52.000000,30.008000 L 50.900000,30.008000 C 50.000000,30.008000 49.800000,30.008000 50.700000,30.008000 L 50.100000,30.008000 C 49.200000,29.908000 48.500000,29.608000 47.900000,29.108000 C 47.400000,28.708000 47.300000,28.208000 47.800000,27.608000 C 48.100000,27.208000 48.600000,26.908000 49.200000,26.608000 C 50.100000,26.208000 51.200000,25.908000 52.400000,25.608000 L 56.500000,24.608000 L 56.500000,25.608000 L 53.100000,26.408000 C 51.500000,26.808000 50.600000,27.208000 50.300000,27.608000 C 49.800000,28.008000 49.800000,28.408000 50.300000,28.608000 C 50.600000,28.908000 51.300000,29.008000 52.300000,29.008000 C 52.700000,29.008000 53.700000,29.008000 54.900000,29.008000 L 58.500000,29.008000 C 59.000000,28.908000 59.700000,28.908000 60.500000,28.708000 L 63.800000,28.008000 L 66.000000,27.208000 C 66.300000,27.108000 66.700000,27.008000 66.700000,26.608000 C 67.100000,26.108000 66.800000,25.708000 65.900000,25.408000 C 65.000000,25.008000 64.100000,25.008000 63.200000,25.008000 L 59.500000,25.208000 L 59.600000,24.408000 L 61.700000,24.208000 C 64.600000,23.808000 66.900000,24.008000 68.600000,24.908000 L 69.000000,25.108000 z "
+ id="path2499" />
+ <linearGradient
+ id="linearGradient4361"
+ gradientUnits="userSpaceOnUse"
+ x1="41.803699"
+ y1="23.916500"
+ x2="1680.2037"
+ y2="23.916500"
+ gradientTransform="matrix(2.050000e-2,2.500000e-3,-6.000000e-4,4.700000e-3,21.16370,234.0289)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop4363" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop4365" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop4367" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ <path
+ i:knockout="Off"
+ d="M 69.500000,25.608000 C 69.300000,25.408000 69.300000,25.308000 69.000000,25.108000 L 68.600000,24.908000 C 66.900000,24.008000 64.600000,23.808000 61.700000,24.208000 L 59.600000,24.408000 L 59.500000,25.208000 L 63.200000,25.008000 C 64.100000,25.008000 65.000000,25.008000 65.900000,25.408000 C 66.800000,25.708000 67.100000,26.108000 66.700000,26.608000 C 66.700000,27.008000 66.300000,27.108000 66.000000,27.208000 L 63.800000,28.008000 L 60.500000,28.708000 C 59.700000,28.908000 59.000000,28.908000 58.500000,29.008000 L 54.900000,29.008000 C 53.700000,29.008000 52.700000,29.008000 52.300000,29.008000 C 51.300000,29.008000 50.600000,28.908000 50.300000,28.608000 C 49.800000,28.408000 49.800000,28.008000 50.300000,27.608000 C 50.600000,27.208000 51.500000,26.808000 53.100000,26.408000 L 56.500000,25.608000 L 56.500000,24.608000 L 52.400000,25.608000 C 51.200000,25.908000 50.100000,26.208000 49.200000,26.608000 C 48.600000,26.908000 48.100000,27.208000 47.800000,27.608000 C 47.300000,28.208000 47.400000,28.708000 47.900000,29.108000 C 48.500000,29.608000 49.200000,29.908000 50.100000,30.008000 L 50.700000,30.008000 C 49.800000,30.008000 50.000000,30.008000 50.900000,30.008000 L 52.000000,30.008000 C 52.800000,30.108000 54.300000,30.108000 56.500000,30.108000 C 56.500000,30.108000 56.700000,30.108000 57.000000,30.108000 C 57.300000,30.108000 58.000000,30.108000 58.900000,30.008000 L 61.200000,29.708000 C 62.000000,29.608000 62.600000,29.508000 63.200000,29.308000 C 63.800000,29.108000 64.400000,28.908000 64.800000,28.708000 L 66.200000,28.308000 C 67.000000,28.008000 67.700000,27.708000 68.500000,27.208000 C 68.800000,27.008000 69.100000,26.908000 69.300000,26.508000 C 69.400000,26.408000 69.500000,26.008000 69.500000,25.608000 z "
+ id="path2508"
+ style="fill:url(#XMLID_63_)" />
+ </g>
+ <path
+ id="path2512"
+ d="M 71.300000,20.108000 C 71.200000,20.008000 71.000000,19.908000 70.700000,19.708000 C 70.300000,19.508000 69.300000,19.208000 67.800000,18.808000 C 66.400000,18.408000 63.500000,18.508000 59.000000,19.108000 C 58.800000,19.808000 58.800000,20.608000 59.200000,21.608000 C 59.700000,21.408000 60.300000,21.208000 60.900000,21.208000 C 61.500000,21.108000 62.000000,21.108000 62.300000,21.108000 C 62.500000,21.108000 62.700000,21.108000 63.000000,21.108000 C 63.300000,21.108000 63.800000,21.108000 64.600000,21.108000 C 65.400000,21.008000 66.100000,21.308000 66.700000,21.908000 C 66.700000,22.008000 66.500000,22.108000 66.000000,22.508000 C 65.500000,22.708000 64.700000,23.008000 63.600000,23.408000 C 62.500000,23.708000 61.200000,24.008000 59.700000,24.108000 C 58.300000,24.208000 56.300000,24.408000 54.000000,24.308000 C 51.700000,24.208000 50.300000,24.208000 50.100000,24.008000 C 49.900000,23.808000 50.000000,23.608000 50.500000,23.408000 C 51.000000,23.108000 52.500000,22.708000 54.700000,22.108000 C 55.800000,21.908000 56.500000,21.608000 56.700000,21.808000 C 57.000000,21.308000 57.000000,20.708000 56.700000,20.108000 C 56.700000,19.508000 56.700000,19.108000 56.800000,19.008000 C 53.500000,19.708000 51.700000,20.108000 51.500000,20.308000 C 51.000000,20.408000 50.500000,20.608000 49.800000,20.708000 C 49.200000,21.008000 48.400000,21.208000 47.700000,21.508000 C 47.000000,21.708000 46.400000,22.108000 45.800000,22.608000 C 45.300000,23.108000 45.100000,23.608000 45.100000,23.908000 C 45.100000,24.208000 45.200000,24.608000 45.200000,24.908000 C 45.300000,25.208000 45.500000,25.608000 46.000000,26.008000 C 46.300000,26.508000 47.000000,26.708000 47.700000,26.908000 C 51.000000,27.508000 54.900000,27.508000 59.200000,27.008000 C 63.400000,26.608000 66.500000,25.908000 68.300000,24.908000 L 69.600000,24.308000 C 70.700000,23.808000 71.200000,23.408000 71.300000,23.208000 C 71.700000,22.608000 71.900000,22.308000 72.000000,22.008000 C 72.100000,21.608000 72.000000,21.208000 71.700000,20.808000 C 71.500000,20.408000 71.400000,20.108000 71.300000,20.108000 z M 70.500000,20.608000 C 70.700000,20.708000 70.900000,20.908000 71.000000,21.008000 C 71.000000,21.608000 70.900000,22.008000 70.800000,22.108000 C 70.500000,22.508000 70.300000,22.808000 69.800000,23.008000 C 69.000000,23.508000 68.100000,23.908000 67.300000,24.208000 L 65.700000,24.608000 C 65.200000,24.908000 64.600000,25.108000 63.700000,25.408000 C 63.100000,25.608000 62.400000,25.708000 61.500000,25.808000 L 58.900000,26.108000 C 57.700000,26.208000 57.000000,26.208000 56.700000,26.308000 C 56.300000,26.308000 56.200000,26.408000 56.100000,26.408000 C 53.700000,26.308000 52.000000,26.208000 51.000000,26.208000 L 49.700000,26.108000 C 48.600000,26.208000 48.500000,26.108000 49.400000,26.108000 L 48.800000,26.108000 C 47.700000,26.008000 46.800000,25.708000 46.300000,25.208000 C 45.700000,24.608000 45.700000,24.008000 46.200000,23.308000 C 46.500000,22.908000 47.000000,22.608000 47.700000,22.208000 C 48.700000,21.808000 50.000000,21.408000 51.400000,21.108000 L 56.200000,20.008000 L 56.200000,21.008000 L 52.300000,22.008000 C 50.500000,22.508000 49.300000,23.008000 49.000000,23.408000 C 48.500000,23.908000 48.500000,24.208000 49.000000,24.508000 C 49.300000,24.808000 50.200000,25.008000 51.300000,25.008000 C 51.800000,25.008000 52.800000,24.908000 54.300000,25.008000 L 58.300000,24.908000 C 59.000000,24.908000 59.800000,24.808000 60.700000,24.708000 L 64.500000,23.908000 L 67.000000,23.008000 C 67.500000,22.908000 67.700000,22.608000 67.900000,22.208000 C 68.300000,21.608000 68.000000,21.208000 67.000000,20.808000 C 65.900000,20.508000 64.800000,20.308000 63.700000,20.508000 L 59.500000,20.608000 L 59.700000,19.608000 L 62.100000,19.508000 C 65.500000,19.008000 68.000000,19.308000 70.000000,20.308000 L 70.500000,20.608000 z "
+ i:knockout="Off" />
+ <path
+ style="fill:url(#XMLID_64_)"
+ id="path2521"
+ d="M 71.000000,21.008000 C 70.900000,20.908000 70.700000,20.708000 70.500000,20.608000 L 70.000000,20.308000 C 68.000000,19.308000 65.500000,19.008000 62.100000,19.508000 L 59.700000,19.608000 L 59.500000,20.608000 L 63.700000,20.508000 C 64.800000,20.308000 65.900000,20.508000 67.000000,20.808000 C 68.000000,21.208000 68.300000,21.608000 67.900000,22.208000 C 67.700000,22.608000 67.500000,22.908000 67.000000,23.008000 L 64.500000,23.908000 L 60.700000,24.708000 C 59.800000,24.808000 59.000000,24.908000 58.300000,24.908000 L 54.300000,25.008000 C 52.800000,24.908000 51.800000,25.008000 51.300000,25.008000 C 50.200000,25.008000 49.300000,24.808000 49.000000,24.508000 C 48.500000,24.208000 48.500000,23.908000 49.000000,23.408000 C 49.300000,23.008000 50.500000,22.508000 52.300000,22.008000 L 56.200000,21.008000 L 56.200000,20.008000 L 51.400000,21.108000 C 50.000000,21.408000 48.700000,21.808000 47.700000,22.208000 C 47.000000,22.608000 46.500000,22.908000 46.200000,23.308000 C 45.700000,24.008000 45.700000,24.608000 46.300000,25.208000 C 46.800000,25.708000 47.700000,26.008000 48.800000,26.108000 L 49.400000,26.108000 C 48.500000,26.108000 48.600000,26.208000 49.700000,26.108000 L 51.000000,26.208000 C 52.000000,26.208000 53.700000,26.308000 56.100000,26.408000 C 56.200000,26.408000 56.300000,26.308000 56.700000,26.308000 C 57.000000,26.208000 57.700000,26.208000 58.900000,26.108000 L 61.500000,25.808000 C 62.400000,25.708000 63.100000,25.608000 63.700000,25.408000 C 64.600000,25.108000 65.200000,24.908000 65.700000,24.608000 L 67.300000,24.208000 C 68.100000,23.908000 69.000000,23.508000 69.800000,23.008000 C 70.300000,22.808000 70.500000,22.508000 70.800000,22.108000 C 70.900000,22.008000 71.000000,21.608000 71.000000,21.008000 z "
+ i:knockout="Off" />
+ <g
+ id="g2962">
+ <path
+ i:knockout="Off"
+ d="M 65.900000,8.5080000 C 66.500000,6.2080000 65.600000,4.2080000 63.500000,2.4080000 C 61.300000,0.70800000 59.300000,-0.092000000 57.500000,0.0080000000 C 55.600000,0.20800000 53.800000,0.70800000 52.200000,1.2080000 C 50.500000,1.9080000 49.200000,3.2080000 48.200000,5.5080000 C 47.200000,7.7080000 47.600000,9.9080000 49.400000,11.908000 C 51.200000,14.008000 53.500000,14.908000 56.300000,14.608000 C 59.200000,14.408000 60.800000,14.208000 61.400000,14.208000 C 61.900000,14.108000 62.700000,13.708000 63.700000,13.008000 C 64.600000,12.108000 65.300000,10.608000 65.900000,8.5080000 z M 58.100000,1.2080000 C 58.600000,1.4080000 59.200000,1.4080000 59.700000,1.6080000 L 61.100000,2.1080000 C 64.000000,3.7080000 65.300000,5.7080000 65.200000,8.2080000 C 65.200000,8.4080000 65.000000,8.9080000 64.700000,9.6080000 C 64.500000,10.408000 64.300000,10.808000 64.100000,11.008000 C 63.000000,12.308000 61.800000,13.108000 60.500000,13.508000 C 58.300000,13.708000 56.800000,13.908000 55.800000,13.708000 C 54.400000,13.708000 53.100000,13.508000 52.000000,12.908000 C 51.200000,12.408000 50.300000,11.408000 49.300000,10.008000 C 49.000000,9.6080000 48.700000,9.1080000 48.700000,8.7080000 C 48.500000,8.2080000 48.500000,7.7080000 48.700000,7.2080000 C 48.800000,6.6080000 49.000000,6.1080000 49.300000,5.6080000 L 50.100000,4.2080000 C 50.400000,3.9080000 50.700000,3.5080000 51.200000,3.2080000 C 51.500000,2.8080000 51.900000,2.5080000 52.300000,2.2080000 L 53.700000,1.6080000 L 55.100000,1.2080000 C 56.500000,1.0080000 57.500000,1.0080000 58.100000,1.2080000 z "
+ id="path2964" />
+ <radialGradient
+ id="radialGradient4374"
+ cx="1323.0430"
+ cy="-5313.3013"
+ r="819.20001"
+ fx="1323.0430"
+ fy="-5313.3013"
+ gradientTransform="matrix(6.330000e-2,-1.680000e-2,8.500000e-3,3.210000e-2,10.88700,202.9904)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#008837"
+ id="stop4376" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop4378" />
+ <a:midPointStop
+ offset="0"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </radialGradient>
+ <path
+ i:knockout="Off"
+ d="M 59.700000,1.6080000 C 59.200000,1.4080000 58.600000,1.4080000 58.100000,1.2080000 C 57.500000,1.0080000 56.500000,1.0080000 55.100000,1.2080000 L 53.700000,1.6080000 L 52.300000,2.2080000 C 51.900000,2.5080000 51.500000,2.8080000 51.200000,3.2080000 C 50.700000,3.5080000 50.400000,3.9080000 50.100000,4.2080000 L 49.300000,5.6080000 C 49.000000,6.1080000 48.800000,6.6080000 48.700000,7.2080000 C 48.500000,7.7080000 48.500000,8.2080000 48.700000,8.7080000 C 48.700000,9.1080000 49.000000,9.6080000 49.300000,10.008000 C 50.300000,11.408000 51.200000,12.408000 52.000000,12.908000 C 53.100000,13.508000 54.400000,13.708000 55.800000,13.708000 C 56.800000,13.908000 58.300000,13.708000 60.500000,13.508000 C 61.800000,13.108000 63.000000,12.308000 64.100000,11.008000 C 64.300000,10.808000 64.500000,10.408000 64.700000,9.6080000 C 65.000000,8.9080000 65.200000,8.4080000 65.200000,8.2080000 C 65.300000,5.7080000 64.000000,3.7080000 61.100000,2.1080000 L 59.700000,1.6080000 z "
+ id="path2971"
+ style="fill:url(#XMLID_96_)" />
+ </g>
+ </g>
+ <g
+ id="g4215">
+ <g
+ id="g2445">
+ <g
+ id="g2447">
+ <g
+ id="g2449">
+ <g
+ id="g2451">
+ <path
+ id="path2453"
+ d="M 73.800000,38.208000 C 72.200000,37.608000 70.300000,37.208000 68.300000,37.008000 C 67.100000,36.808000 65.200000,36.708000 62.400000,36.608000 C 61.300000,36.508000 60.200000,36.608000 59.000000,36.708000 C 53.300000,37.508000 48.300000,39.508000 43.700000,42.608000 C 43.600000,42.908000 43.300000,43.008000 43.100000,43.208000 C 38.700000,46.608000 35.000000,50.708000 32.000000,55.408000 C 30.000000,58.208000 28.400000,61.508000 27.000000,64.708000 C 25.500000,68.008000 23.100000,70.608000 20.200000,72.708000 L 6.5000000,82.308000 C 7.5000000,87.608000 9.0000000,92.908000 11.100000,98.008000 C 13.000000,102.60800 15.500000,106.60800 18.400000,109.60800 C 19.000000,110.40800 19.900000,111.10800 20.700000,111.70800 C 23.300000,113.50800 26.400000,114.40800 30.000000,114.60800 L 30.300000,114.60800 C 32.900000,114.40800 35.400000,113.70800 37.900000,112.60800 C 42.600000,110.60800 47.500000,108.70800 52.400000,107.00800 C 54.200000,106.40800 55.800000,105.80800 57.700000,105.20800 C 62.200000,103.70800 66.800000,102.50800 71.500000,101.50800 C 74.900000,100.90800 78.000000,99.608000 80.700000,97.508000 C 86.200000,93.208000 90.300000,87.908000 93.200000,81.508000 C 94.700000,77.608000 95.500000,73.708000 95.300000,69.808000 L 95.300000,68.708000 L 95.600000,68.008000 C 94.900000,61.408000 92.800000,55.608000 89.200000,50.208000 C 87.700000,48.108000 86.100000,46.308000 84.300000,44.908000 C 82.300000,43.108000 80.100000,41.608000 77.700000,40.008000 C 76.600000,39.308000 75.300000,38.708000 73.800000,38.208000 z M 67.800000,39.108000 C 70.000000,39.508000 72.100000,40.008000 74.200000,40.908000 C 75.300000,41.208000 76.200000,41.808000 77.200000,42.508000 C 79.100000,43.808000 81.000000,45.308000 82.700000,46.908000 C 83.200000,47.408000 83.700000,47.808000 84.300000,48.208000 C 86.200000,49.608000 87.600000,51.208000 88.700000,53.208000 C 88.700000,53.508000 89.000000,53.708000 89.200000,53.908000 L 92.700000,61.808000 L 93.700000,67.608000 L 93.300000,68.408000 L 93.300000,74.708000 L 92.000000,78.508000 C 91.500000,81.208000 89.400000,85.108000 85.500000,90.408000 L 86.300000,89.508000 L 84.800000,91.208000 L 85.500000,90.408000 C 80.400000,96.008000 75.800000,99.208000 71.700000,99.908000 C 61.600000,101.50800 54.000000,103.50800 48.900000,105.80800 C 46.700000,106.80800 44.500000,107.60800 42.700000,108.50800 C 40.300000,109.40800 38.300000,110.10800 36.700000,110.70800 C 32.300000,112.20800 28.600000,112.70800 25.600000,112.20800 C 24.900000,112.00800 24.000000,111.70800 23.300000,111.30800 C 21.100000,110.10800 19.000000,108.00800 17.000000,104.90800 C 14.200000,100.60800 12.300000,96.708000 11.300000,93.208000 C 10.300000,89.908000 9.5000000,86.608000 9.1000000,83.608000 C 14.000000,80.508000 18.600000,77.008000 23.100000,73.308000 C 24.500000,72.208000 25.700000,71.008000 26.700000,69.508000 C 28.800000,66.608000 30.600000,63.608000 32.000000,60.408000 C 34.000000,55.608000 37.000000,51.508000 40.600000,48.008000 C 43.500000,45.108000 46.900000,43.108000 50.500000,41.608000 C 51.900000,41.108000 53.400000,40.708000 54.900000,40.308000 C 59.100000,39.008000 63.300000,38.608000 67.500000,39.108000 C 67.500000,39.108000 67.700000,39.108000 67.800000,39.108000 z "
+ i:knockout="Off" />
+ <linearGradient
+ gradientTransform="matrix(3.960000e-2,-1.970000e-2,1.890000e-2,3.780000e-2,36.87870,242.5071)"
+ y2="-3377.9888"
+ x2="3651.6226"
+ y1="-3377.9888"
+ x1="2013.2227"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4387">
+ <stop
+ id="stop4389"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop4391"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <path
+ style="fill:url(#XMLID_59_)"
+ id="path2460"
+ d="M 74.200000,40.908000 C 72.100000,40.008000 70.000000,39.508000 67.800000,39.108000 C 67.700000,39.108000 67.500000,39.108000 67.500000,39.108000 C 63.300000,38.608000 59.100000,39.008000 54.900000,40.308000 C 53.400000,40.708000 51.900000,41.108000 50.500000,41.608000 C 46.900000,43.108000 43.500000,45.108000 40.600000,48.008000 C 37.000000,51.508000 34.000000,55.608000 32.000000,60.408000 C 30.600000,63.608000 28.800000,66.608000 26.700000,69.508000 C 25.700000,71.008000 24.500000,72.208000 23.100000,73.308000 C 18.600000,77.008000 14.000000,80.508000 9.1000000,83.608000 C 9.5000000,86.608000 10.300000,89.908000 11.300000,93.208000 C 12.300000,96.708000 14.200000,100.60800 17.000000,104.90800 C 19.000000,108.00800 21.100000,110.10800 23.300000,111.30800 C 24.000000,111.70800 24.900000,112.00800 25.600000,112.20800 C 28.600000,112.70800 32.300000,112.20800 36.700000,110.70800 C 38.300000,110.10800 40.300000,109.40800 42.700000,108.50800 C 44.500000,107.60800 46.700000,106.80800 48.900000,105.80800 C 54.000000,103.50800 61.600000,101.50800 71.700000,99.908000 C 75.800000,99.208000 80.400000,96.008000 85.500000,90.408000 C 89.400000,85.108000 91.500000,81.208000 92.000000,78.508000 L 93.300000,74.708000 L 93.300000,68.408000 L 93.700000,67.608000 L 92.700000,61.808000 L 89.200000,53.908000 C 89.000000,53.708000 88.700000,53.508000 88.700000,53.208000 C 87.600000,51.208000 86.200000,49.608000 84.300000,48.208000 C 83.700000,47.808000 83.200000,47.408000 82.700000,46.908000 C 81.000000,45.308000 79.100000,43.808000 77.200000,42.508000 C 76.200000,41.808000 75.300000,41.208000 74.200000,40.908000 z "
+ i:knockout="Off" />
+ <linearGradient
+ gradientTransform="matrix(3.960000e-2,-1.970000e-2,1.890000e-2,3.780000e-2,36.87870,242.5071)"
+ y2="-2710.3794"
+ x2="3651.6226"
+ y1="-2710.3794"
+ x1="2013.2227"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4394">
+ <stop
+ id="stop4396"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop4398"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <path
+ style="fill:url(#XMLID_60_)"
+ id="path2467"
+ d="M 85.500000,90.408000 L 84.800000,91.208000 L 86.300000,89.508000 L 85.500000,90.408000 z "
+ i:knockout="Off" />
+ </g>
+ </g>
+ </g>
+ </g>
+ <g
+ id="g2523">
+ <g
+ id="g2525">
+ <g
+ id="g2527">
+ <g
+ id="g2529">
+ <path
+ id="path2531"
+ d="M 78.700000,35.108000 C 74.100000,37.408000 70.900000,40.908000 69.000000,45.708000 C 68.200000,47.808000 67.300000,49.908000 66.500000,52.008000 L 66.400000,52.008000 C 66.000000,52.808000 65.600000,53.508000 65.000000,54.208000 C 65.000000,54.208000 65.000000,54.408000 64.900000,54.408000 C 64.500000,55.108000 64.300000,55.908000 64.200000,56.708000 C 64.000000,58.408000 64.100000,60.008000 64.700000,61.508000 C 64.800000,62.108000 65.100000,62.608000 65.300000,63.208000 C 65.700000,64.508000 66.500000,65.708000 67.300000,66.908000 C 67.400000,66.908000 67.400000,66.908000 67.400000,67.008000 C 68.300000,68.208000 69.200000,69.408000 70.200000,70.508000 C 70.200000,70.608000 70.300000,70.608000 70.300000,70.708000 C 70.700000,71.108000 70.900000,71.508000 71.200000,71.908000 C 71.200000,72.008000 71.200000,72.008000 71.200000,72.008000 C 71.300000,72.108000 71.300000,72.208000 71.400000,72.408000 C 72.300000,73.908000 73.500000,74.908000 75.000000,75.408000 C 75.100000,75.408000 75.100000,75.408000 75.200000,75.508000 C 77.600000,76.008000 80.000000,76.608000 82.400000,77.308000 C 84.400000,78.008000 86.300000,78.808000 88.400000,79.508000 C 93.500000,81.208000 98.800000,81.408000 104.20000,80.008000 C 105.90000,79.608000 107.60000,79.108000 109.20000,78.408000 C 111.80000,76.808000 113.50000,75.408000 114.30000,74.008000 C 114.80000,73.008000 115.40000,71.908000 116.00000,70.608000 C 116.60000,69.408000 116.60000,66.608000 115.90000,62.108000 C 115.30000,57.708000 113.70000,54.208000 111.20000,51.808000 L 93.700000,33.008000 C 93.200000,32.908000 92.700000,32.608000 92.300000,32.508000 C 92.100000,32.408000 91.800000,32.408000 91.700000,32.308000 C 91.600000,32.308000 91.600000,32.308000 91.600000,32.308000 L 91.500000,32.308000 C 89.400000,31.708000 87.300000,31.908000 85.100000,32.608000 C 83.600000,33.108000 82.200000,33.608000 80.700000,34.208000 C 80.000000,34.608000 79.300000,34.808000 78.700000,35.108000 z M 72.200000,43.608000 C 73.200000,42.008000 74.500000,40.608000 76.000000,39.508000 C 78.400000,37.808000 80.900000,36.408000 83.600000,35.408000 C 83.900000,35.308000 84.100000,35.208000 84.400000,35.108000 C 86.300000,34.508000 88.200000,34.008000 90.000000,33.708000 L 92.200000,34.008000 C 92.500000,34.408000 92.800000,34.708000 93.000000,35.108000 C 96.500000,39.608000 100.20000,43.608000 104.30000,47.508000 C 105.20000,48.408000 106.70000,50.008000 108.90000,52.008000 C 111.10000,54.208000 112.70000,57.308000 113.90000,61.208000 C 115.20000,65.208000 114.70000,69.008000 112.70000,72.808000 C 108.80000,76.908000 105.40000,78.908000 102.30000,78.908000 C 99.300000,78.908000 96.600000,78.808000 94.200000,78.508000 C 92.500000,78.108000 90.800000,77.708000 89.200000,77.208000 C 84.000000,75.808000 78.700000,74.408000 73.600000,72.808000 C 69.700000,68.508000 67.300000,65.108000 66.500000,62.708000 C 65.700000,60.308000 65.500000,58.608000 65.800000,57.708000 C 66.200000,56.808000 66.400000,55.908000 66.500000,55.008000 C 68.100000,51.108000 70.000000,47.408000 72.200000,43.608000 z "
+ i:knockout="Off" />
+ <linearGradient
+ gradientTransform="matrix(6.970000e-2,0.000000,0.000000,2.740000e-2,47.07010,253.6812)"
+ y2="-7203.4263"
+ x2="1309.2438"
+ y1="-7203.4263"
+ x1="-329.15619"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4406">
+ <stop
+ id="stop4408"
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <stop
+ id="stop4410"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0" />
+ <a:midPointStop
+ style="stop-color:#FFFFFF"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <path
+ style="fill:url(#XMLID_65_)"
+ id="path2538"
+ d="M 76.000000,39.508000 C 74.500000,40.608000 73.200000,42.008000 72.200000,43.608000 C 70.000000,47.408000 68.100000,51.108000 66.500000,55.008000 C 66.400000,55.908000 66.200000,56.808000 65.800000,57.708000 C 65.500000,58.608000 65.700000,60.308000 66.500000,62.708000 C 67.300000,65.108000 69.700000,68.508000 73.600000,72.808000 C 78.700000,74.408000 84.000000,75.808000 89.200000,77.208000 C 90.800000,77.708000 92.500000,78.108000 94.200000,78.508000 C 96.600000,78.808000 99.300000,78.908000 102.30000,78.908000 C 105.40000,78.908000 108.80000,76.908000 112.70000,72.808000 C 114.70000,69.008000 115.20000,65.208000 113.90000,61.208000 C 112.70000,57.308000 111.10000,54.208000 108.90000,52.008000 C 106.70000,50.008000 105.20000,48.408000 104.30000,47.508000 C 100.20000,43.608000 96.500000,39.608000 93.000000,35.108000 C 92.800000,34.708000 92.500000,34.408000 92.200000,34.008000 L 90.000000,33.708000 C 88.200000,34.008000 86.300000,34.508000 84.400000,35.108000 C 84.100000,35.208000 83.900000,35.308000 83.600000,35.408000 C 80.900000,36.408000 78.400000,37.808000 76.000000,39.508000 z "
+ i:knockout="Off" />
+ </g>
+ </g>
+ </g>
+ </g>
+ <g
+ id="g2984">
+ <path
+ id="path2986"
+ d="M 28.000000,65.508000 C 27.900000,65.708000 27.900000,65.908000 27.700000,66.108000 C 27.000000,67.508000 26.100000,68.608000 25.000000,69.508000 C 25.000000,69.608000 25.100000,69.708000 25.200000,69.708000 C 25.500000,69.908000 25.600000,70.008000 25.900000,70.208000 C 25.900000,70.208000 25.900000,70.308000 25.900000,70.408000 C 25.900000,70.408000 25.900000,70.508000 25.900000,70.608000 C 26.400000,71.008000 27.000000,71.408000 27.600000,71.608000 C 28.300000,71.908000 28.900000,72.208000 29.400000,72.708000 C 30.800000,74.008000 32.300000,75.208000 33.600000,76.608000 C 34.000000,77.008000 34.300000,77.308000 34.700000,77.608000 C 36.200000,78.308000 37.700000,78.808000 39.500000,79.008000 C 40.200000,79.108000 41.000000,79.208000 41.700000,79.208000 C 44.900000,79.508000 47.000000,78.208000 48.300000,75.608000 C 49.800000,72.808000 50.800000,69.908000 51.300000,66.708000 C 51.500000,65.708000 51.600000,64.708000 51.600000,63.708000 C 51.600000,60.908000 50.400000,58.708000 48.000000,57.208000 C 47.500000,56.608000 47.000000,56.408000 46.300000,56.208000 C 44.800000,55.908000 43.300000,55.208000 41.800000,54.508000 C 40.300000,53.608000 38.700000,53.108000 37.100000,52.908000 C 37.100000,52.908000 37.100000,52.908000 37.100000,52.808000 C 37.100000,52.708000 37.100000,52.708000 37.000000,52.608000 L 37.000000,52.608000 C 36.500000,52.508000 36.200000,52.408000 35.700000,52.408000 C 35.500000,52.508000 35.200000,52.608000 34.900000,52.708000 L 34.900000,52.808000 C 34.600000,52.708000 34.300000,52.708000 34.000000,52.808000 L 34.000000,52.908000 C 33.900000,52.908000 33.800000,53.008000 33.700000,53.008000 C 33.700000,53.008000 33.600000,53.008000 33.500000,53.108000 C 33.500000,53.108000 33.400000,53.208000 33.300000,53.308000 C 33.300000,53.408000 33.300000,53.408000 33.200000,53.508000 C 33.100000,53.608000 33.000000,53.708000 33.000000,53.908000 C 32.700000,54.608000 32.400000,55.108000 32.000000,55.708000 C 31.900000,56.008000 31.700000,56.208000 31.600000,56.608000 C 31.600000,56.608000 31.600000,56.608000 31.700000,56.708000 C 31.700000,56.708000 31.800000,56.808000 31.900000,56.908000 C 31.900000,56.908000 32.000000,56.908000 32.100000,57.008000 C 32.100000,57.008000 32.300000,57.008000 32.300000,56.908000 C 32.400000,56.908000 32.500000,56.708000 32.500000,56.608000 C 32.900000,56.008000 33.300000,55.408000 33.700000,54.708000 C 33.700000,54.608000 33.800000,54.508000 33.800000,54.508000 C 33.700000,54.708000 33.600000,55.008000 33.500000,55.208000 C 32.600000,56.408000 31.900000,57.608000 31.300000,58.808000 C 31.300000,58.908000 31.300000,58.908000 31.300000,59.008000 L 31.300000,59.108000 C 31.100000,59.208000 31.000000,59.408000 31.000000,59.508000 C 30.900000,59.508000 30.900000,59.608000 30.900000,59.608000 C 30.700000,60.008000 30.500000,60.508000 30.400000,61.008000 C 29.900000,62.608000 29.000000,64.108000 28.000000,65.508000 z M 27.400000,69.608000 C 27.400000,69.408000 27.300000,69.108000 27.100000,68.908000 C 27.000000,68.908000 27.100000,68.908000 27.100000,68.808000 C 27.400000,68.508000 27.600000,68.108000 27.800000,67.708000 C 28.400000,66.708000 29.000000,65.708000 29.700000,64.908000 C 29.800000,64.608000 30.000000,64.408000 30.100000,64.108000 C 30.300000,64.008000 30.400000,63.708000 30.500000,63.508000 C 30.400000,63.108000 30.500000,62.908000 30.600000,62.608000 C 30.900000,62.208000 31.100000,61.808000 31.200000,61.408000 C 31.300000,61.308000 31.300000,61.208000 31.400000,61.108000 C 31.400000,61.108000 31.400000,61.008000 31.500000,61.008000 C 31.500000,60.608000 31.700000,60.108000 32.000000,59.708000 C 33.000000,57.808000 34.100000,55.908000 35.200000,53.908000 C 35.800000,53.908000 36.500000,54.008000 37.200000,54.008000 C 37.500000,54.108000 38.000000,54.208000 38.300000,54.308000 C 38.700000,54.608000 39.200000,55.008000 39.700000,55.108000 C 42.600000,56.108000 45.400000,57.408000 48.100000,58.708000 C 48.100000,58.808000 48.100000,58.808000 48.100000,58.908000 C 48.100000,58.908000 48.100000,59.008000 48.200000,59.008000 C 48.300000,59.208000 48.500000,59.508000 48.800000,59.608000 C 49.000000,59.808000 49.100000,60.008000 49.300000,60.108000 C 49.300000,60.208000 49.500000,60.408000 49.500000,60.508000 C 49.600000,60.608000 49.600000,60.608000 49.600000,60.608000 C 49.600000,60.708000 49.600000,60.708000 49.600000,60.808000 C 49.700000,60.908000 49.700000,60.908000 49.700000,61.008000 C 50.500000,62.508000 50.700000,64.108000 50.300000,65.908000 C 50.000000,67.208000 49.700000,68.508000 49.200000,69.708000 C 49.000000,70.908000 48.700000,72.008000 48.300000,73.008000 C 47.900000,73.708000 47.500000,74.408000 47.000000,75.008000 C 47.000000,75.108000 47.000000,75.208000 47.000000,75.208000 C 47.000000,75.308000 47.000000,75.408000 47.000000,75.408000 C 47.000000,75.508000 47.000000,75.508000 47.000000,75.608000 C 46.500000,76.208000 45.900000,76.608000 45.200000,77.008000 C 42.800000,78.008000 40.300000,78.108000 37.700000,77.508000 C 37.300000,77.308000 36.800000,77.108000 36.400000,76.908000 C 34.100000,75.508000 32.100000,73.608000 30.400000,71.408000 C 29.700000,71.008000 29.000000,70.508000 28.400000,70.008000 C 28.000000,69.908000 27.700000,69.708000 27.500000,69.708000 C 27.500000,69.608000 27.500000,69.608000 27.400000,69.608000 z "
+ i:knockout="Off" />
+ <linearGradient
+ gradientTransform="matrix(8.500000e-3,-1.880000e-2,1.330000e-2,6.000000e-3,-5.852400,270.4716)"
+ y2="-2914.9419"
+ x2="10760.650"
+ y1="-2914.9419"
+ x1="9122.2500"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4415">
+ <stop
+ id="stop4417"
+ style="stop-color:#000000"
+ offset="0.0275" />
+ <stop
+ id="stop4419"
+ style="stop-color:#979797"
+ offset="0.5412" />
+ <stop
+ id="stop4421"
+ style="stop-color:#000000"
+ offset="1" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="0.0275" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#979797"
+ offset="0.5412" />
+ <a:midPointStop
+ style="stop-color:#979797"
+ offset="0.5" />
+ <a:midPointStop
+ style="stop-color:#000000"
+ offset="1" />
+ </linearGradient>
+ <path
+ style="fill:url(#XMLID_98_)"
+ id="path2995"
+ d="M 27.100000,68.908000 C 27.300000,69.108000 27.400000,69.408000 27.400000,69.608000 C 27.500000,69.608000 27.500000,69.608000 27.500000,69.708000 C 27.700000,69.708000 28.000000,69.908000 28.400000,70.008000 C 29.000000,70.508000 29.700000,71.008000 30.400000,71.408000 C 32.100000,73.608000 34.100000,75.508000 36.400000,76.908000 C 36.800000,77.108000 37.300000,77.308000 37.700000,77.508000 C 40.300000,78.108000 42.800000,78.008000 45.200000,77.008000 C 45.900000,76.608000 46.500000,76.208000 47.000000,75.608000 C 47.000000,75.508000 47.000000,75.508000 47.000000,75.408000 C 47.000000,75.408000 47.000000,75.308000 47.000000,75.208000 C 47.000000,75.208000 47.000000,75.108000 47.000000,75.008000 C 47.500000,74.408000 47.900000,73.708000 48.300000,73.008000 C 48.700000,72.008000 49.000000,70.908000 49.200000,69.708000 C 49.700000,68.508000 50.000000,67.208000 50.300000,65.908000 C 50.700000,64.108000 50.500000,62.508000 49.700000,61.008000 C 49.700000,60.908000 49.700000,60.908000 49.600000,60.808000 C 49.600000,60.708000 49.600000,60.708000 49.600000,60.608000 C 49.600000,60.608000 49.600000,60.608000 49.500000,60.508000 C 49.500000,60.408000 49.300000,60.208000 49.300000,60.108000 C 49.100000,60.008000 49.000000,59.808000 48.800000,59.608000 C 48.500000,59.508000 48.300000,59.208000 48.200000,59.008000 C 48.100000,59.008000 48.100000,58.908000 48.100000,58.908000 C 48.100000,58.808000 48.100000,58.808000 48.100000,58.708000 C 45.400000,57.408000 42.600000,56.108000 39.700000,55.108000 C 39.200000,55.008000 38.700000,54.608000 38.300000,54.308000 C 38.000000,54.208000 37.500000,54.108000 37.200000,54.008000 C 36.500000,54.008000 35.800000,53.908000 35.200000,53.908000 C 34.100000,55.908000 33.000000,57.808000 32.000000,59.708000 C 31.700000,60.108000 31.500000,60.608000 31.500000,61.008000 C 31.400000,61.008000 31.400000,61.108000 31.400000,61.108000 C 31.300000,61.208000 31.300000,61.308000 31.200000,61.408000 C 31.100000,61.808000 30.900000,62.208000 30.600000,62.608000 C 30.500000,62.908000 30.400000,63.108000 30.500000,63.508000 C 30.400000,63.708000 30.300000,64.008000 30.100000,64.108000 C 30.000000,64.408000 29.800000,64.608000 29.700000,64.908000 C 29.000000,65.708000 28.400000,66.708000 27.800000,67.708000 C 27.600000,68.108000 27.400000,68.508000 27.100000,68.808000 C 27.100000,68.908000 27.000000,68.908000 27.100000,68.908000 z "
+ i:knockout="Off" />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/icons/dogtail-tail-48.png b/icons/dogtail-tail-48.png
new file mode 100644
index 00000000000..40c85da6895
--- /dev/null
+++ b/icons/dogtail-tail-48.png
Binary files differ
diff --git a/icons/dogtail-tail.svg b/icons/dogtail-tail.svg
new file mode 100644
index 00000000000..46d4d8132d4
--- /dev/null
+++ b/icons/dogtail-tail.svg
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
+ xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64.000000pt"
+ height="64.000000pt"
+ id="svg4424"
+ sodipodi:version="0.32"
+ inkscape:version="0.42"
+ sodipodi:docbase="/home/zack/Desktop/dtlogos"
+ sodipodi:docname="dogtail-tail.svg">
+ <defs
+ id="defs4426">
+ <radialGradient
+ id="XMLID_97_"
+ cx="1115.2148"
+ cy="-4111.3169"
+ r="819.20001"
+ fx="1115.2148"
+ fy="-4111.3169"
+ gradientTransform="matrix(6.810000e-2,-2.100000e-2,9.100000e-3,4.020000e-2,160.8859,238.5417)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#008837"
+ id="stop2978" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop2980" />
+ <a:midPointStop
+ offset="0"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </radialGradient>
+ <linearGradient
+ id="XMLID_62_"
+ gradientUnits="userSpaceOnUse"
+ x1="-3667.3906"
+ y1="-23573.730"
+ x2="-2028.9906"
+ y2="-23573.730"
+ gradientTransform="matrix(2.750000e-2,2.070000e-2,-4.800000e-3,6.300000e-3,161.4600,276.9943)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop2489" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop2491" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop2493" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ <linearGradient
+ id="XMLID_61_"
+ gradientUnits="userSpaceOnUse"
+ x1="-4085.7969"
+ y1="-27067.957"
+ x2="-2447.3970"
+ y2="-27067.957"
+ gradientTransform="matrix(2.390000e-2,1.810000e-2,-4.200000e-3,5.500000e-3,157.6075,283.4794)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop2476" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop2478" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop2480" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.6081777"
+ inkscape:cx="116.41672"
+ inkscape:cy="61.505776"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="753"
+ inkscape:window-height="513"
+ inkscape:window-x="209"
+ inkscape:window-y="46" />
+ <metadata
+ id="metadata4429">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Máirín Duffy</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Red Hat, Inc.</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
+ <dc:title>dogtail tail</dc:title>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/GPL/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/SourceCode" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ transform="translate(-156.7689,-29.69089)"
+ id="g4297"
+ inkscape:export-filename="/home/zack/Desktop/dogtail-tail-logo-128.png"
+ inkscape:export-xdpi="288.03000"
+ inkscape:export-ydpi="288.03000">
+ <g
+ id="g2394">
+ <path
+ i:knockout="Off"
+ d="M 209.00000,48.408000 C 208.90000,48.408000 208.90000,48.408000 208.70000,48.408000 C 208.30000,47.708000 207.90000,47.108000 207.30000,46.508000 C 207.30000,46.408000 207.10000,46.308000 206.90000,46.108000 C 206.70000,45.208000 206.30000,44.608000 205.50000,44.108000 C 205.40000,44.108000 205.20000,44.008000 205.10000,44.008000 C 205.00000,44.008000 205.00000,44.008000 204.90000,44.108000 C 204.90000,44.108000 204.90000,44.108000 204.90000,44.208000 L 204.80000,44.408000 C 204.80000,44.508000 204.80000,44.508000 204.80000,44.608000 C 204.90000,44.708000 205.10000,44.908000 205.10000,45.008000 L 205.10000,45.008000 C 204.10000,47.408000 202.90000,49.808000 201.90000,52.108000 C 200.80000,54.508000 199.70000,56.708000 198.60000,59.108000 C 198.00000,60.008000 197.60000,61.008000 197.20000,62.108000 C 196.30000,64.608000 195.20000,67.008000 194.00000,69.308000 C 192.80000,71.608000 191.60000,73.908000 190.10000,76.108000 C 188.70000,78.208000 187.30000,80.508000 186.10000,82.708000 C 185.40000,83.908000 184.80000,85.008000 184.20000,86.108000 C 182.80000,88.408000 181.70000,90.708000 180.80000,93.108000 C 179.70000,95.508000 178.80000,98.008000 177.80000,100.50800 C 177.60000,101.00800 177.60000,101.60800 177.60000,102.10800 C 177.70000,102.20800 177.70000,102.50800 177.70000,102.60800 C 177.80000,102.60800 177.80000,102.60800 177.90000,102.60800 C 178.00000,102.60800 178.20000,102.60800 178.30000,102.50800 C 179.30000,102.10800 180.40000,102.00800 181.40000,102.20800 C 182.10000,102.40800 182.70000,102.40800 183.30000,102.20800 C 183.50000,102.20800 183.80000,102.10800 183.80000,102.10800 C 183.90000,102.00800 183.90000,102.00800 184.10000,102.00800 L 184.10000,102.00800 C 184.20000,101.60800 184.30000,101.20800 184.30000,101.00800 C 185.00000,98.408000 185.70000,95.908000 186.50000,93.508000 C 187.40000,91.008000 188.30000,88.608000 189.40000,86.208000 C 190.50000,83.908000 191.70000,81.608000 193.20000,79.308000 C 194.50000,77.008000 195.80000,74.708000 197.10000,72.608000 C 198.40000,70.308000 199.70000,68.008000 200.90000,65.608000 C 202.10000,63.408000 203.40000,61.008000 204.40000,58.708000 C 205.40000,56.308000 206.60000,54.008000 207.80000,51.608000 C 208.20000,51.108000 208.70000,50.608000 209.10000,50.008000 C 209.40000,49.508000 209.40000,48.908000 209.20000,48.508000 C 209.10000,48.408000 209.10000,48.408000 209.00000,48.408000 z M 207.40000,50.208000 L 207.40000,50.408000 C 206.90000,51.108000 206.40000,52.108000 205.90000,53.008000 C 204.70000,55.208000 203.40000,57.508000 202.30000,59.908000 C 201.30000,62.208000 200.10000,64.608000 198.90000,66.808000 C 197.70000,69.108000 196.40000,71.408000 195.10000,73.608000 C 193.80000,75.908000 192.40000,78.208000 191.20000,80.508000 C 189.80000,82.708000 188.60000,85.008000 187.60000,87.508000 C 186.40000,89.708000 185.40000,92.208000 184.70000,94.808000 C 184.40000,95.708000 184.20000,96.608000 183.80000,97.608000 C 183.40000,98.608000 183.20000,99.808000 183.30000,101.00800 L 183.30000,100.90800 C 183.20000,101.00800 183.20000,101.00800 183.20000,101.00800 C 183.10000,101.10800 183.10000,101.10800 183.10000,101.20800 C 181.90000,101.00800 180.70000,101.00800 179.60000,101.10800 C 179.40000,101.20800 179.20000,101.20800 178.80000,101.40800 C 178.90000,101.10800 179.10000,101.00800 179.10000,100.60800 C 179.40000,100.00800 179.70000,99.008000 179.90000,98.308000 C 180.80000,95.808000 181.90000,93.508000 182.90000,91.108000 C 183.70000,89.408000 184.70000,87.608000 185.60000,85.908000 C 186.30000,84.408000 187.00000,83.008000 187.90000,81.608000 C 189.30000,79.408000 190.70000,77.108000 192.20000,74.908000 C 193.50000,72.708000 194.70000,70.408000 195.80000,68.008000 C 196.80000,65.608000 197.90000,63.108000 198.80000,60.708000 C 199.10000,60.108000 199.30000,59.408000 199.70000,58.708000 C 200.70000,56.408000 201.70000,53.908000 202.90000,51.608000 C 203.80000,49.808000 204.60000,48.008000 205.40000,46.108000 C 205.40000,46.508000 205.60000,46.608000 206.00000,46.808000 C 206.00000,46.908000 206.00000,46.908000 206.10000,47.008000 C 206.30000,47.208000 206.50000,47.408000 206.60000,47.608000 C 206.60000,47.908000 206.60000,48.008000 206.60000,48.208000 C 206.70000,48.808000 206.80000,49.408000 207.40000,49.808000 C 207.30000,50.008000 207.40000,50.108000 207.40000,50.208000 z "
+ id="path2396" />
+ <path
+ i:knockout="Off"
+ d="M 207.40000,50.408000 L 207.40000,50.208000 C 207.40000,50.108000 207.30000,50.008000 207.40000,49.808000 C 206.80000,49.408000 206.70000,48.808000 206.60000,48.208000 C 206.60000,48.008000 206.60000,47.908000 206.60000,47.608000 C 206.50000,47.408000 206.30000,47.208000 206.10000,47.008000 C 206.00000,46.908000 206.00000,46.908000 206.00000,46.808000 C 205.60000,46.608000 205.40000,46.508000 205.40000,46.108000 C 204.60000,48.008000 203.80000,49.808000 202.90000,51.608000 C 201.70000,53.908000 200.70000,56.408000 199.70000,58.708000 C 199.30000,59.408000 199.10000,60.108000 198.80000,60.708000 C 197.90000,63.108000 196.80000,65.608000 195.80000,68.008000 C 194.70000,70.408000 193.50000,72.708000 192.20000,74.908000 C 190.70000,77.108000 189.30000,79.408000 187.90000,81.608000 C 187.00000,83.008000 186.30000,84.408000 185.60000,85.908000 C 184.70000,87.608000 183.70000,89.408000 182.90000,91.108000 C 181.90000,93.508000 180.80000,95.808000 179.90000,98.308000 C 179.70000,99.008000 179.40000,100.00800 179.10000,100.60800 C 179.10000,101.00800 178.90000,101.10800 178.80000,101.40800 C 179.20000,101.20800 179.40000,101.20800 179.60000,101.10800 C 180.70000,101.00800 181.90000,101.00800 183.10000,101.20800 C 183.10000,101.10800 183.10000,101.10800 183.20000,101.00800 C 183.20000,101.00800 183.20000,101.00800 183.30000,100.90800 L 183.30000,101.00800 C 183.20000,99.808000 183.40000,98.608000 183.80000,97.608000 C 184.20000,96.608000 184.40000,95.708000 184.70000,94.808000 C 185.40000,92.208000 186.40000,89.708000 187.60000,87.508000 C 188.60000,85.008000 189.80000,82.708000 191.20000,80.508000 C 192.40000,78.208000 193.80000,75.908000 195.10000,73.608000 C 196.40000,71.408000 197.70000,69.108000 198.90000,66.808000 C 200.10000,64.608000 201.30000,62.208000 202.30000,59.908000 C 203.40000,57.508000 204.70000,55.208000 205.90000,53.008000 C 206.40000,52.108000 206.90000,51.108000 207.40000,50.408000 z "
+ id="path2398"
+ style="fill:#333333" />
+ </g>
+ <g
+ id="g2471">
+ <path
+ i:knockout="Off"
+ d="M 209.30000,80.708000 C 209.20000,80.608000 209.10000,80.408000 208.80000,79.908000 C 208.60000,79.508000 207.70000,78.608000 206.30000,77.108000 C 204.90000,75.708000 201.70000,74.008000 196.40000,71.908000 C 195.70000,72.608000 195.30000,73.508000 195.00000,74.708000 C 195.70000,74.808000 196.50000,75.008000 197.20000,75.408000 C 197.90000,75.708000 198.40000,76.008000 198.70000,76.108000 C 198.90000,76.208000 199.30000,76.408000 199.60000,76.608000 C 199.90000,76.808000 200.40000,77.108000 201.30000,77.608000 C 202.20000,78.008000 202.80000,78.808000 203.20000,79.808000 C 203.10000,79.908000 202.70000,80.008000 202.10000,80.008000 C 201.30000,80.008000 200.30000,79.708000 198.70000,79.508000 C 197.30000,79.108000 195.70000,78.608000 194.00000,77.908000 C 192.30000,77.008000 190.20000,75.908000 187.70000,74.508000 C 185.10000,72.908000 183.70000,72.008000 183.60000,71.608000 C 183.40000,71.208000 183.70000,71.008000 184.40000,71.208000 C 185.20000,71.208000 186.90000,71.708000 189.80000,72.508000 C 191.30000,72.908000 192.10000,73.108000 192.20000,73.408000 C 192.70000,73.008000 193.10000,72.408000 193.30000,71.608000 C 193.60000,70.908000 193.80000,70.408000 194.10000,70.508000 C 190.00000,69.108000 187.80000,68.408000 187.30000,68.408000 C 186.90000,68.208000 186.30000,68.108000 185.30000,67.908000 C 184.40000,67.608000 183.50000,67.508000 182.50000,67.308000 C 181.60000,67.108000 180.70000,67.108000 179.70000,67.408000 C 178.90000,67.608000 178.30000,67.908000 178.20000,68.308000 C 177.90000,68.708000 177.70000,69.108000 177.70000,69.508000 C 177.50000,70.008000 177.60000,70.508000 177.70000,71.208000 C 177.80000,72.008000 178.30000,72.608000 179.10000,73.108000 C 182.40000,76.008000 186.70000,78.508000 191.60000,80.608000 C 196.60000,82.808000 200.30000,84.008000 203.10000,84.108000 L 204.80000,84.208000 C 206.20000,84.408000 207.00000,84.408000 207.30000,84.208000 C 208.00000,83.908000 208.60000,83.508000 208.80000,83.208000 C 209.20000,82.908000 209.30000,82.408000 209.30000,81.808000 C 209.30000,81.208000 209.30000,80.908000 209.30000,80.708000 z M 208.10000,80.808000 C 208.30000,81.008000 208.40000,81.408000 208.30000,81.608000 C 207.90000,82.208000 207.70000,82.608000 207.40000,82.608000 C 206.90000,82.908000 206.40000,83.008000 205.90000,83.008000 C 204.60000,83.008000 203.30000,82.908000 202.30000,82.708000 L 200.20000,82.208000 C 199.60000,82.108000 198.70000,82.008000 197.70000,81.708000 C 196.90000,81.508000 195.90000,81.208000 194.90000,80.808000 L 191.90000,79.508000 C 190.60000,78.908000 189.70000,78.508000 189.30000,78.408000 C 188.90000,78.108000 188.70000,78.008000 188.70000,78.008000 C 185.90000,76.408000 184.20000,75.208000 183.20000,74.608000 L 181.70000,73.708000 C 180.60000,73.008000 180.50000,73.008000 181.40000,73.608000 L 180.70000,73.108000 C 179.70000,72.308000 178.90000,71.408000 178.60000,70.508000 C 178.30000,69.508000 178.70000,68.908000 179.70000,68.408000 C 180.30000,68.108000 181.10000,68.108000 182.10000,68.208000 C 183.50000,68.408000 185.10000,68.708000 186.80000,69.208000 L 192.70000,71.008000 L 192.10000,72.208000 L 187.20000,70.708000 C 184.90000,70.108000 183.40000,70.008000 182.70000,70.208000 C 181.90000,70.408000 181.70000,70.808000 182.00000,71.508000 C 182.30000,72.008000 183.00000,72.608000 184.30000,73.408000 C 184.80000,73.708000 185.90000,74.408000 187.60000,75.408000 L 192.10000,77.908000 C 192.80000,78.208000 193.70000,78.608000 194.70000,79.008000 L 199.40000,80.608000 L 202.70000,81.208000 C 203.30000,81.308000 203.80000,81.308000 204.20000,81.008000 C 205.00000,80.608000 204.90000,79.908000 204.10000,78.808000 C 203.10000,77.708000 202.10000,76.908000 200.70000,76.408000 L 196.10000,74.008000 L 196.80000,73.008000 L 199.60000,74.408000 C 203.50000,75.908000 206.20000,77.808000 207.70000,80.108000 L 208.10000,80.808000 z "
+ id="path2473" />
+ <linearGradient
+ id="linearGradient4449"
+ gradientUnits="userSpaceOnUse"
+ x1="-4085.7969"
+ y1="-27067.957"
+ x2="-2447.3970"
+ y2="-27067.957"
+ gradientTransform="matrix(2.390000e-2,1.810000e-2,-4.200000e-3,5.500000e-3,157.6075,283.4794)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop4451" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop4453" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop4455" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ <path
+ i:knockout="Off"
+ d="M 208.30000,81.608000 C 208.40000,81.408000 208.30000,81.008000 208.10000,80.808000 L 207.70000,80.108000 C 206.20000,77.808000 203.50000,75.908000 199.60000,74.408000 L 196.80000,73.008000 L 196.10000,74.008000 L 200.70000,76.408000 C 202.10000,76.908000 203.10000,77.708000 204.10000,78.808000 C 204.90000,79.908000 205.00000,80.608000 204.20000,81.008000 C 203.80000,81.308000 203.30000,81.308000 202.70000,81.208000 L 199.40000,80.608000 L 194.70000,79.008000 C 193.70000,78.608000 192.80000,78.208000 192.10000,77.908000 L 187.60000,75.408000 C 185.90000,74.408000 184.80000,73.708000 184.30000,73.408000 C 183.00000,72.608000 182.30000,72.008000 182.00000,71.508000 C 181.70000,70.808000 181.90000,70.408000 182.70000,70.208000 C 183.40000,70.008000 184.90000,70.108000 187.20000,70.708000 L 192.10000,72.208000 L 192.70000,71.008000 L 186.80000,69.208000 C 185.10000,68.708000 183.50000,68.408000 182.10000,68.208000 C 181.10000,68.108000 180.30000,68.108000 179.70000,68.408000 C 178.70000,68.908000 178.30000,69.508000 178.60000,70.508000 C 178.90000,71.408000 179.70000,72.308000 180.70000,73.108000 L 181.40000,73.608000 C 180.50000,73.008000 180.60000,73.008000 181.70000,73.708000 L 183.20000,74.608000 C 184.20000,75.208000 185.90000,76.408000 188.70000,78.008000 C 188.70000,78.008000 188.90000,78.108000 189.30000,78.408000 C 189.70000,78.508000 190.60000,78.908000 191.90000,79.508000 L 194.90000,80.808000 C 195.90000,81.208000 196.90000,81.508000 197.70000,81.708000 C 198.70000,82.008000 199.60000,82.108000 200.20000,82.208000 L 202.30000,82.708000 C 203.30000,82.908000 204.60000,83.008000 205.90000,83.008000 C 206.40000,83.008000 206.90000,82.908000 207.40000,82.608000 C 207.70000,82.608000 207.90000,82.208000 208.30000,81.608000 z "
+ id="path2482"
+ style="fill:url(#XMLID_61_)" />
+ </g>
+ <g
+ id="g2484">
+ <path
+ i:knockout="Off"
+ d="M 214.70000,75.508000 C 214.60000,75.408000 214.40000,75.008000 214.20000,74.608000 C 213.80000,74.108000 212.80000,73.008000 211.30000,71.408000 C 209.70000,69.808000 206.00000,67.808000 199.90000,65.408000 C 199.10000,66.108000 198.60000,67.108000 198.30000,68.608000 C 199.20000,68.708000 200.10000,69.008000 200.80000,69.408000 C 201.60000,69.808000 202.20000,70.108000 202.40000,70.308000 C 202.70000,70.408000 203.10000,70.608000 203.50000,70.808000 C 203.80000,71.008000 204.50000,71.408000 205.60000,72.008000 C 206.60000,72.508000 207.30000,73.308000 207.70000,74.508000 C 207.60000,74.608000 207.20000,74.608000 206.30000,74.608000 C 205.50000,74.608000 204.30000,74.508000 202.60000,74.108000 C 200.90000,73.708000 199.10000,73.108000 197.10000,72.208000 C 195.20000,71.308000 192.70000,70.008000 189.70000,68.308000 C 186.80000,66.608000 185.30000,65.508000 185.20000,65.008000 C 185.10000,64.608000 185.30000,64.508000 186.20000,64.608000 C 187.00000,64.708000 189.10000,65.208000 192.30000,66.108000 C 193.90000,66.508000 194.90000,66.908000 195.00000,67.108000 C 195.70000,66.608000 196.20000,66.008000 196.30000,65.108000 C 196.60000,64.208000 196.90000,63.708000 197.20000,63.808000 C 192.60000,62.208000 190.00000,61.408000 189.40000,61.308000 C 188.90000,61.208000 188.20000,61.008000 187.10000,60.808000 C 186.20000,60.608000 185.10000,60.408000 183.90000,60.108000 C 182.80000,59.908000 181.80000,60.008000 180.80000,60.208000 C 179.80000,60.508000 179.20000,60.908000 178.90000,61.308000 C 178.60000,61.708000 178.50000,62.208000 178.30000,62.708000 C 178.20000,63.108000 178.30000,63.808000 178.40000,64.608000 C 178.60000,65.508000 179.20000,66.208000 180.10000,66.908000 C 183.80000,70.108000 188.70000,73.008000 194.30000,75.508000 C 200.10000,77.908000 204.40000,79.208000 207.40000,79.508000 L 209.50000,79.608000 C 211.10000,79.708000 212.10000,79.708000 212.40000,79.508000 C 213.30000,79.008000 213.80000,78.708000 214.20000,78.408000 C 214.60000,78.008000 214.70000,77.508000 214.70000,76.808000 C 214.70000,76.108000 214.70000,75.708000 214.70000,75.508000 z M 213.30000,75.608000 C 213.50000,75.908000 213.60000,76.208000 213.60000,76.608000 C 213.20000,77.208000 212.80000,77.608000 212.50000,77.708000 C 212.00000,78.008000 211.40000,78.208000 210.80000,78.108000 C 209.30000,78.108000 207.80000,78.008000 206.60000,77.808000 L 204.30000,77.308000 C 203.60000,77.108000 202.60000,77.008000 201.30000,76.608000 C 200.40000,76.508000 199.40000,76.108000 198.20000,75.608000 L 194.70000,74.108000 C 193.20000,73.508000 192.20000,73.008000 191.80000,72.808000 C 191.30000,72.608000 191.10000,72.408000 191.00000,72.408000 C 187.90000,70.608000 185.80000,69.208000 184.70000,68.508000 L 183.10000,67.508000 C 181.70000,66.708000 181.60000,66.608000 182.70000,67.208000 L 181.90000,66.708000 C 180.70000,65.908000 179.80000,64.908000 179.40000,63.808000 C 179.10000,62.608000 179.50000,61.908000 180.70000,61.408000 C 181.30000,61.008000 182.30000,61.008000 183.30000,61.108000 C 185.10000,61.308000 186.90000,61.708000 188.80000,62.408000 L 195.60000,64.408000 L 194.90000,65.808000 L 189.30000,64.108000 C 186.70000,63.408000 184.90000,63.208000 184.20000,63.508000 C 183.30000,63.708000 182.90000,64.108000 183.30000,64.908000 C 183.60000,65.608000 184.40000,66.308000 185.90000,67.108000 C 186.60000,67.508000 187.90000,68.208000 189.70000,69.408000 L 194.90000,72.208000 C 195.70000,72.608000 196.70000,73.108000 197.90000,73.608000 L 203.30000,75.508000 L 207.10000,76.008000 C 207.80000,76.208000 208.30000,76.108000 208.70000,75.708000 C 209.70000,75.308000 209.70000,74.608000 208.70000,73.308000 C 207.60000,72.108000 206.30000,71.108000 204.80000,70.608000 L 199.50000,67.708000 L 200.30000,66.608000 L 203.50000,68.208000 C 208.10000,70.008000 211.20000,72.208000 212.90000,74.908000 L 213.30000,75.608000 z "
+ id="path2486" />
+ <linearGradient
+ id="linearGradient4460"
+ gradientUnits="userSpaceOnUse"
+ x1="-3667.3906"
+ y1="-23573.730"
+ x2="-2028.9906"
+ y2="-23573.730"
+ gradientTransform="matrix(2.750000e-2,2.070000e-2,-4.800000e-3,6.300000e-3,161.4600,276.9943)">
+ <stop
+ offset="0.0275"
+ style="stop-color:#4A4A4A"
+ id="stop4462" />
+ <stop
+ offset="0.5412"
+ style="stop-color:#FFFFFF"
+ id="stop4464" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop4466" />
+ <a:midPointStop
+ offset="0.0275"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#4A4A4A" />
+ <a:midPointStop
+ offset="0.5412"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#FFFFFF" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </linearGradient>
+ <path
+ i:knockout="Off"
+ d="M 213.60000,76.608000 C 213.60000,76.208000 213.50000,75.908000 213.30000,75.608000 L 212.90000,74.908000 C 211.20000,72.208000 208.10000,70.008000 203.50000,68.208000 L 200.30000,66.608000 L 199.50000,67.708000 L 204.80000,70.608000 C 206.30000,71.108000 207.60000,72.108000 208.70000,73.308000 C 209.70000,74.608000 209.70000,75.308000 208.70000,75.708000 C 208.30000,76.108000 207.80000,76.208000 207.10000,76.008000 L 203.30000,75.508000 L 197.90000,73.608000 C 196.70000,73.108000 195.70000,72.608000 194.90000,72.208000 L 189.70000,69.408000 C 187.90000,68.208000 186.60000,67.508000 185.90000,67.108000 C 184.40000,66.308000 183.60000,65.608000 183.30000,64.908000 C 182.90000,64.108000 183.30000,63.708000 184.20000,63.508000 C 184.90000,63.208000 186.70000,63.408000 189.30000,64.108000 L 194.90000,65.808000 L 195.60000,64.408000 L 188.80000,62.408000 C 186.90000,61.708000 185.10000,61.308000 183.30000,61.108000 C 182.30000,61.008000 181.30000,61.008000 180.70000,61.408000 C 179.50000,61.908000 179.10000,62.608000 179.40000,63.808000 C 179.80000,64.908000 180.70000,65.908000 181.90000,66.708000 L 182.70000,67.208000 C 181.60000,66.608000 181.70000,66.708000 183.10000,67.508000 L 184.70000,68.508000 C 185.80000,69.208000 187.90000,70.608000 191.00000,72.408000 C 191.10000,72.408000 191.30000,72.608000 191.80000,72.808000 C 192.20000,73.008000 193.20000,73.508000 194.70000,74.108000 L 198.20000,75.608000 C 199.40000,76.108000 200.40000,76.508000 201.30000,76.608000 C 202.60000,77.008000 203.60000,77.108000 204.30000,77.308000 L 206.60000,77.808000 C 207.80000,78.008000 209.30000,78.108000 210.80000,78.108000 C 211.40000,78.208000 212.00000,78.008000 212.50000,77.708000 C 212.80000,77.608000 213.20000,77.208000 213.60000,76.608000 z "
+ id="path2495"
+ style="fill:url(#XMLID_62_)" />
+ </g>
+ <g
+ id="g2973">
+ <path
+ i:knockout="Off"
+ d="M 217.40000,47.408000 C 218.00000,44.608000 217.10000,42.008000 214.70000,39.808000 C 212.40000,37.608000 210.30000,36.608000 208.30000,36.808000 C 206.30000,37.008000 204.40000,37.608000 202.70000,38.308000 C 200.80000,39.008000 199.40000,40.808000 198.30000,43.608000 C 197.30000,46.408000 197.70000,49.108000 199.70000,51.608000 C 201.60000,54.208000 204.10000,55.408000 207.10000,55.108000 C 210.20000,54.708000 211.90000,54.608000 212.60000,54.508000 C 213.10000,54.508000 213.90000,54.008000 214.90000,53.008000 C 216.00000,52.008000 216.80000,50.108000 217.40000,47.408000 z M 209.00000,38.308000 C 209.60000,38.508000 210.10000,38.608000 210.70000,38.708000 L 212.20000,39.408000 C 215.30000,41.408000 216.80000,44.008000 216.60000,47.108000 C 216.60000,47.208000 216.40000,47.908000 216.10000,48.908000 C 215.80000,49.708000 215.60000,50.208000 215.40000,50.508000 C 214.30000,52.108000 213.00000,53.208000 211.50000,53.608000 C 209.30000,54.008000 207.60000,54.108000 206.60000,54.008000 C 205.00000,54.008000 203.60000,53.608000 202.50000,52.908000 C 201.60000,52.208000 200.60000,51.008000 199.40000,49.208000 C 199.20000,48.708000 198.90000,48.208000 198.80000,47.608000 C 198.70000,47.008000 198.70000,46.408000 198.80000,45.708000 C 199.00000,45.008000 199.20000,44.408000 199.40000,43.708000 L 200.40000,42.008000 C 200.70000,41.608000 201.10000,41.108000 201.50000,40.708000 C 201.90000,40.208000 202.30000,39.908000 202.80000,39.608000 L 204.20000,38.708000 L 205.70000,38.308000 C 207.30000,38.008000 208.40000,38.008000 209.00000,38.308000 z "
+ id="path2975" />
+ <radialGradient
+ id="radialGradient4471"
+ cx="1115.2148"
+ cy="-4111.3169"
+ r="819.20001"
+ fx="1115.2148"
+ fy="-4111.3169"
+ gradientTransform="matrix(6.810000e-2,-2.100000e-2,9.100000e-3,4.020000e-2,160.8859,238.5417)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#008837"
+ id="stop4473" />
+ <stop
+ offset="1"
+ style="stop-color:#000000"
+ id="stop4475" />
+ <a:midPointStop
+ offset="0"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="0.5"
+ style="stop-color:#008837" />
+ <a:midPointStop
+ offset="1"
+ style="stop-color:#000000" />
+ </radialGradient>
+ <path
+ i:knockout="Off"
+ d="M 210.70000,38.708000 C 210.10000,38.608000 209.60000,38.508000 209.00000,38.308000 C 208.40000,38.008000 207.30000,38.008000 205.70000,38.308000 L 204.20000,38.708000 L 202.80000,39.608000 C 202.30000,39.908000 201.90000,40.208000 201.50000,40.708000 C 201.10000,41.108000 200.70000,41.608000 200.40000,42.008000 L 199.40000,43.708000 C 199.20000,44.408000 199.00000,45.008000 198.80000,45.708000 C 198.70000,46.408000 198.70000,47.008000 198.80000,47.608000 C 198.90000,48.208000 199.20000,48.708000 199.40000,49.208000 C 200.60000,51.008000 201.60000,52.208000 202.50000,52.908000 C 203.60000,53.608000 205.00000,54.008000 206.60000,54.008000 C 207.60000,54.108000 209.30000,54.008000 211.50000,53.608000 C 213.00000,53.208000 214.30000,52.108000 215.40000,50.508000 C 215.60000,50.208000 215.80000,49.708000 216.10000,48.908000 C 216.40000,47.908000 216.60000,47.208000 216.60000,47.108000 C 216.80000,44.008000 215.30000,41.408000 212.20000,39.408000 L 210.70000,38.708000 z "
+ id="path2982"
+ style="fill:url(#XMLID_97_)" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/scripts/dogtail-detect-session b/scripts/dogtail-detect-session
new file mode 100644
index 00000000000..8160201361e
--- /dev/null
+++ b/scripts/dogtail-detect-session
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+"""
+dogtail-detect session
+
+This script checks for some main pieces of a running GNOME session,
+specifically gnome-panel and metacity.
+
+It checks to see that the gnome-panel node has at least some child nodes.
+For example, the main gnome-panel node by default has two direct descendents:
+the top panel, and the bottom panel.
+Metacity's existence is also checked. However, metacity currently never has
+any child nodes.
+
+If a proper session is running, the scripts exits with a status of 0.
+If no session is found, a non-zero exit code is returned.
+
+Author: Zack Cerza <zcerza@redhat.com>
+"""
+
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from dogtail.procedural import *
+import sys
+
+
+def GNOME():
+ """
+ "Is an accessibility-enabled GNOME session running?"
+ """
+ running = False
+ try:
+ assert focus.desktop
+ assert focus.desktop.children
+
+ focus.application('gnome-panel')
+ assert focus.application.children
+
+ focus.application('metacity')
+ print focus.application.node
+ assert focus.application.node
+ running = True
+ print "GNOME Session detected."
+ except AttributeError or AssertionError or FocusError:
+ print "ERROR: No session found."
+ return running
+
+
+def KDE():
+ """
+ "Is an accessibility-enabled KDE session running?"
+ """
+ running = False
+ return running
+
+
+def JustSomeApps():
+ """
+ "Is at least one accessibility-enabled application running?"
+ """
+ assert focus.desktop
+ assert focus.desktop.children
+
+if GNOME() or KDE() or JustSomeApps():
+ sys.exit()
+else:
+ sys.exit(1)
diff --git a/scripts/dogtail-logout b/scripts/dogtail-logout
new file mode 100644
index 00000000000..c76a4519e08
--- /dev/null
+++ b/scripts/dogtail-logout
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+# Logs out the full gnome session. Be sure to have your documents saved, as running
+# may cause loosing the changes, or it may halt the logout process.
+
+from dogtail.tree import *
+from dogtail.rawinput import pressKey
+from time import sleep
+import getpass
+
+# A gnome-shell object
+shell = root.application('gnome-shell')
+# Click onto a super menu label that we find under the g-s top panel object.
+# We need these indexes as g-s a11y support is a wee bit messy.
+shell[0][1][2].child(getpass.getuser(), roleName='label').click()
+# We can child this all the way down from the app as there's no other Log
+# Out... label
+shell.child('Log Out...', roleName='label').click()
+# This takes care of the 60 second dialog.
+# Sometimes a dialog warning about unsaved work in gedit etc. pops out, but that has the same
+# push button in which case this will take care of that dialog. If another dialog pops-out
+# in the affected application however, that might put the logout process on hold again. Unfortunatelly
+# we cannot do anything about that with dotail at that point as a11y registry got disabled already
+# by the logout process.
+shell[0][1].child(roleName='dialog', recursive=False).child(
+ 'Log Out', roleName='push button').click()
+
+# Give the session some time to end before we kill it.
+sleep(10)
diff --git a/scripts/dogtail-run-headless b/scripts/dogtail-run-headless
new file mode 100644
index 00000000000..edab53ddeed
--- /dev/null
+++ b/scripts/dogtail-run-headless
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+"""
+dogtail-run-headless
+
+This script runs a session within an X server, allowing dogtail scripts to be
+run on systems with no graphic cards, among other things.
+
+Scripts are run in the current directory. After they are finished, dogtail can
+optionally log out of the session, which will also termninate the X server.
+"""
+
+import optparse
+from dogtail import sessions
+import sys
+import os.path
+
+
+def findXServers(path="/usr/bin"):
+ l = [os.path.join(path, f) for f in os.listdir(path) if f[0] == 'X']
+ s = set(os.path.realpath(p) for p in l)
+ return list(s)
+
+
+def parse():
+ yesno = ('y', 'n')
+ sessions = ("GNOME", "KDE")
+ usage = "usage: %prog: [options] {script [args]}"
+ parser = optparse.OptionParser(usage=usage)
+
+ parser.add_option("-s", "--session", type="choice",
+ dest="session",
+ choices=sessions,
+ help="which session to use")
+ parser.add_option("-x", "--x-server", type="choice",
+ dest="xserver",
+ choices=findXServers(), help="which X server to use")
+ parser.add_option("-l", "--logout", type="choice",
+ dest="logout",
+ choices=yesno,
+ help="attempt to log out of the session gracefully after" +
+ "script completion")
+ parser.add_option("-t", "--terminate", type="choice",
+ dest="terminate",
+ choices=yesno,
+ help="after script completion, and after any attempt to log" +
+ "out, terminate the session")
+
+ parser.set_defaults(session=sessions[0], logout='y', terminate='y')
+ options, args = parser.parse_args()
+ if not args:
+ parser.print_usage()
+ sys.exit(1)
+ return options, args
+
+
+def main():
+ options, args = parse()
+ if 'XDG_RUNTIME_DIR' in os.environ:
+ del os.environ['XDG_RUNTIME_DIR']
+ if options.session == "GNOME":
+ session = sessions.Session(sessionBinary='/usr/bin/gnome-session',
+ scriptCmdList=args, scriptDelay=10)
+ if options.session == "KDE":
+ session = sessions.Session(sessionBinary='/usr/bin/startkde',
+ scriptCmdList=args, scriptDelay=25)
+ if options.xserver:
+ session.xserver.server = options.xserver
+ pid = session.start()
+ scriptExitCode = session.script.exitCode
+ if options.logout == 'y':
+ session.attemptLogout()
+ if options.terminate == 'y':
+ session.stop()
+ else:
+ session.wait()
+ sys.exit(scriptExitCode)
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/dogtail-run-headless-next b/scripts/dogtail-run-headless-next
new file mode 100644
index 00000000000..ae481cf1c44
--- /dev/null
+++ b/scripts/dogtail-run-headless-next
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+descr = """
+Unlike the original headless script this will make use of an Display Manager
+(DM - currently gdm) to handle starting the X server and user session. It's motivated
+by changes related to systemd - that disallows running a gnome session from an
+environment spawned by 'su'. The original headless will not work in these cases
+anymore on systemd systems
+
+Instead this script uses the AutoLogin feature of the DM, so that when it starts DM's
+service the session will login for particular user at once. It then uses the
+environment properties from the new session and runs the target script inthere.
+
+Will work with distros where 'service gdm/kdm start/stop' takes an effect, and quite
+likely only on systemd systems that use systemd-logind service.
+
+Even if you are still able to use dogtail-run-headless in your usecase, you might
+consider switching to this script - as making use of DM is likely to be more reliable
+and less buggy compared to headless itself taking care of everything.
+"""
+
+drop_overview = '''from dogtail.utils import absoluteMotion, keyPress
+absoluteMotion(100,100)
+keyPress('esc')'''
+
+import argparse
+import sys
+import os
+import glob
+import subprocess
+import time
+import ConfigParser
+import shutil
+import re
+from dogtail.sessions import Script
+
+preserve_envs = ['PYTHONPATH', 'TEST']
+
+
+def getSessionEnvironment(sessionBinary):
+
+ def isSessionProcess(fileName):
+ try:
+ if os.path.realpath(fileName + 'exe') != ('/usr/bin/plasma-desktop'
+ if sessionBinary.split('/')[-1] == 'startkde'
+ else sessionBinary):
+ return False
+ except OSError:
+ return False
+ pid = fileName.split('/')[2]
+ if pid == 'self' or pid == str(os.getpid()):
+ return False
+ return True
+
+ def getEnvDict(fileName):
+ try:
+ envString = open(fileName, 'r').read()
+ except IOError:
+ return {}
+ envItems = envString.split('\x00')
+ envDict = {}
+ for item in envItems:
+ if not '=' in item:
+ continue
+ k, v = item.split('=', 1)
+ envDict[k] = v
+ return envDict
+
+ def copyVars(envDict):
+ '''Copy a couple of old variables we want to preserve'''
+ for env in preserve_envs:
+ if os.environ.has_key(env):
+ envDict[env] = os.environ[env]
+ return envDict
+
+ envDict = False
+ for path in glob.glob('/proc/*/'):
+ if not isSessionProcess(path):
+ continue
+ envFile = path + 'environ'
+ envDict = getEnvDict(envFile)
+ if not envDict:
+ raise RuntimeError("Can't find our environment!")
+ return copyVars(envDict)
+
+
+def execCodeWithEnv(code, env=None):
+ with open("/tmp/execcode.dogtail", "w") as text_file:
+ text_file.write(code)
+ subprocess.Popen('python /tmp/execcode.dogtail'.split(),
+ env=(os.environ if env is None else env)).wait()
+
+
+class DisplayManagerSession(object):
+
+ gdm_config = '/etc/gdm/custom.conf'
+ kdm_config = '/etc/kde/kdm/kdmrc'
+ gdm_options = {'section': 'daemon', 'enable':
+ 'AutomaticLoginEnable', 'user': 'AutomaticLogin'}
+ kdm_options = {'section': 'X-:0-Core', 'enable':
+ 'AutoLoginEnable', 'user': 'AutoLoginUser'}
+ scriptDelay = 20
+ user = 'test'
+
+ def isProcessRunning(self, process):
+ '''Gives true if process can be greped out of full ps dump '''
+ s = subprocess.Popen(["ps", "axw"], stdout=subprocess.PIPE)
+ for x in s.stdout:
+ if re.search(process, x):
+ return True
+ return False
+
+ def waitForProcess(self, process, invert=False):
+ '''Waits until a process appears'''
+ while self.isProcessRunning(process) is invert:
+ time.sleep(1)
+
+ def __init__(self, dm='gdm', session='gnome', session_binary='gnome-shell', user=None):
+ self.session_binary = session_binary
+ self.session = session
+ self.accountfile = '/var/lib/AccountsService/users/%s' % self.user
+ if user is not None:
+ self.user = user
+ if dm == 'gdm':
+ self.tmp_file = '/tmp/%s' % os.path.basename(self.gdm_config)
+ self.options = self.gdm_options
+ self.config = self.gdm_config
+ elif dm == 'kdm':
+ self.tmp_file = '/tmp/%s' % os.path.basename(self.kdm_config)
+ self.options = self.kdm_options
+ self.config = self.kdm_config
+ self.dm = dm
+
+ def setup(self, restore=False):
+ shutil.copy(self.config, self.tmp_file)
+ config = ConfigParser.SafeConfigParser()
+ config.optionxform = str
+ config.read(self.tmp_file)
+ if not restore:
+ config.set(self.options['section'], self.options['enable'], 'true')
+ config.set(
+ self.options['section'], self.options['user'], self.user)
+ else:
+ config.remove_option(
+ self.options['section'], self.options['enable'])
+ config.remove_option(self.options['section'], self.options['user'])
+ output = open(self.tmp_file, 'w')
+ config.write(output)
+ output.flush()
+ subprocess.Popen('sudo mv -f %s %s' %
+ (self.tmp_file, self.config), shell=True).wait()
+ if not restore:
+ if 'kwin' in self.session_binary:
+ try:
+ os.makedirs(os.getenv('HOME') + '/.kde/env/')
+ except:
+ pass
+ subprocess.Popen(
+ 'echo "export QT_ACCESSIBILITY=1" > ~/.kde/env/qt-at-spi.sh', shell=True).wait()
+ if self.dm == 'gdm':
+ need_restart = False
+ tempfile = '/tmp/%s_headless' % self.user
+ subprocess.Popen('cp -f %s %s' % (self.accountfile, tempfile), shell=True).wait()
+ account_config = ConfigParser.SafeConfigParser()
+ account_config.optionxform = str
+ account_config.read(tempfile)
+ try:
+ saved_session = account_config.get('User', 'XSession')
+ if self.session is None:
+ if 'kde' in saved_session and 'gnome-shell' in self.session_binary:
+ self.session_binary = '/usr/bin/kwin'
+ elif 'gnome' in saved_session and 'kwin' in self.session_binary:
+ self.session_binary = '/usr/bin/gnome-shell'
+ elif saved_session != self.session:
+ account_config.set('User', 'XSession', self.session)
+ need_restart = True
+ except ConfigParser.NoSectionError:
+ account_config.add_section('User')
+ account_config.set('User', 'XSession', self.session if self.session else '')
+ account_config.set('User', 'SystemAccount', 'false')
+ need_restart = True
+ if need_restart:
+ output = open(tempfile, 'w')
+ account_config.write(output)
+ output.flush()
+ subprocess.Popen('sudo mv -f %s %s' % (tempfile, self.accountfile), shell=True).wait()
+ time.sleep(1)
+ os.system('sudo service accounts-daemon restart')
+ time.sleep(6) # prevent a possible race condition
+ os.system('sudo service systemd-logind restart')
+ time.sleep(6) # these are fast, but we still need to make sure no races happen
+ else:
+ subprocess.Popen('sudo rm -f %s' % tempfile, shell=True).wait()
+ elif self.dm == 'kdm':
+ if self.session is not None:
+ subprocess.Popen(
+ 'echo "[Desktop]\nSession=%s" > /home/%s/.dmrc' % (self.session, self.user), shell=True).wait()
+
+ def start(self, restart=False):
+ if restart:
+ subprocess.Popen(('sudo service %s stop' % (self.dm)).split())
+ time.sleep(0.5)
+ subprocess.Popen(('sudo service %s start' % (self.dm)).split())
+ self.waitForProcess(self.session_binary.split('/')[-1])
+ # some extra time for an environment (shell/kwin) to load all resources
+ # etc.
+ if self.dm == 'kdm':
+ time.sleep(10) # KDE keeps loading screen on untill all is loaded
+ else:
+ time.sleep(4) # GNOME shows stuff as it appears
+
+ def setA11y(self, enable):
+ subprocess.Popen('/usr/bin/gsettings set org.gnome.desktop.interface toolkit-accessibility %s'
+ % ('true' if enable else 'false'), shell=True, env=os.environ)
+# if enable: # mouse is at 0x0 at the start - which brings in the overview
+# execCodeWithEnv(drop_overview, env = os.environ)
+# time.sleep(2) # time for the overview to go away
+
+ def stop(self):
+ subprocess.Popen(('sudo service %s stop' % (self.dm)).split()).wait()
+ self.waitForProcess('/usr/bin/%s' % self.dm, invert=True)
+ time.sleep(3) # extra safe time
+ # did i.e. gnome-shell get stuck running?
+ if self.isProcessRunning(self.session_binary.split('/')[-1]):
+ print(
+ 'dogtail-run-headless-next: WARNING: %s still running, proceeding with kill -9' %
+ self.session_binary.split('/')[-1])
+ subprocess.Popen(
+ ('sudo pkill --signal 9 %s' % (self.session_binary.split('/')[-1])).split()).wait()
+ time.sleep(1)
+
+
+def parse():
+ parser = argparse.ArgumentParser(
+ prog='$ dogtail-run-headless-next', description=descr, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('script', help="""Command to execute the script""")
+ parser.add_argument('--session', required=False,
+ help="""What session to use. Not specifying results in system default session on first login
+ or test user's last session with follow-up logins.
+ Otherwise you can set any xsession desktop file name here (i.e. 'gnome-classic', 'gnome', 'lxde' etc.).
+ 'kde' defaults to 'kde-plasma for legacy reasons.'""")
+ parser.add_argument('--session-binary', required=False,
+ help="""Specify an in-session ever-running binary (full path) to get the environment from. Only needed for non-gnome/kde sessions.""")
+ parser.add_argument('--dm', required=False,
+ help="""What display manager to use for spawning session. Supported are 'gdm' (default) and 'kdm'.""")
+ parser.add_argument('--restart', action='store_true',
+ help="""Restart previously running display manager session before script execution.""")
+ parser.add_argument('--dont-start', action='store_true',
+ help="""Use already running session (doesn't have to be under Display Manager)""")
+ parser.add_argument('--dont-kill', action='store_true',
+ help="""Do not kill session when script exits.""")
+ parser.add_argument('--disable-a11y', action='store_true',
+ help="""Disable accessibility technologies on script(not session) exit.""")
+ return parser.parse_args()
+
+
+def main():
+ args = parse()
+ scriptCmdList = args.script.split()
+
+ if args.session is None or 'gnome' in args.session:
+ if args.session_binary is None:
+ args.session_binary = '/usr/bin/gnome-shell'
+ elif 'kde' in args.session:
+ if args.session_binary is None:
+ args.session_binary = '/usr/bin/kwin'
+ if args.session == 'kde':
+ args.session = 'kde-plasma'
+ else:
+ if args.session_binary is None:
+ print('dogtail-run-headless-next: Need to specify a --session-binary ever-present in the session to get env from.')
+ sys.exit(-1)
+ if args.dm == 'gdm' or args.dm is None:
+ dm_name = 'gdm'
+ elif args.dm == 'kdm':
+ dm_name = 'kdm'
+ else:
+ print('dogtail-run-headless-next: I do not recognize the display manager!')
+ sys.exit(-1)
+
+ print('dogtail-run-headless-next: Using display manager: %s' % dm_name)
+
+ dm = DisplayManagerSession(dm_name, args.session, args.session_binary)
+
+ if args.dont_start is not True:
+ dm.setup()
+ dm.start(restart=args.restart)
+
+ print('dogtail-run-headless-next: Using %s to bind to the session' % dm.session_binary)
+
+ try:
+ os.environ = getSessionEnvironment(dm.session_binary)
+ except:
+ print(
+ 'dogtail-run-headless-next: Could not get the environment from %s process' %
+ dm.session_binary)
+ dm.stop()
+ dm.setup(restore=True)
+ sys.exit(1)
+
+ if dm_name == 'gdm':
+ dm.setA11y(True)
+
+ script = Script(scriptCmdList)
+ scriptPid = script.start()
+ print('dogtail-run-headless-next: Started the script with PID %d' % scriptPid)
+ exitCode = script.wait()
+ print('dogtail-run-headless-next: The script has finnished with return code %d' % exitCode)
+
+ if args.disable_a11y is True:
+ dm.setA11y(False)
+
+ if args.dont_kill is False:
+ dm.stop()
+ dm.setup(restore=True)
+
+ sys.exit(exitCode)
+
+if __name__ == "__main__":
+ main()
diff --git a/setup.py b/setup.py
new file mode 100755
index 00000000000..5513d710d25
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from distutils.core import setup
+from distutils.command.bdist_rpm import bdist_rpm
+
+def examples():
+ import os
+ exList = os.listdir(os.curdir + '/examples/')
+ result = []
+ for ex in exList:
+ if ex.split('.')[-1] == 'py':
+ result = result + ['examples/' + ex]
+ return result
+
+def examples_data():
+ import os
+ dataList = os.listdir(os.curdir + '/examples/data/')
+ result = []
+ for data in dataList:
+ result = result + ['examples/data/' + data]
+ return result
+
+def tests():
+ import os
+ exList = os.listdir(os.curdir + '/tests/')
+ result = []
+ for ex in exList:
+ if ex.split('.')[-1] == 'py':
+ result = result + ['tests/' + ex]
+ return result
+
+def sniff_icons():
+ import os
+ list = os.listdir(os.curdir + '/sniff/icons/')
+ result = []
+ for file in list:
+ if file.split('.')[-1] in ('xpm'):
+ result = result + ['sniff/icons/' + file]
+ return result
+
+def icons(ext_tuple):
+ import os
+ list = os.listdir(os.curdir + '/icons/')
+ result = []
+ for file in list:
+ if file.split('.')[-1] in ext_tuple:
+ result = result + ['icons/' + file]
+ return result
+
+def scripts():
+ import os
+ list = os.listdir(os.curdir + '/scripts/')
+ result = ['sniff/sniff']
+ for file in list:
+ result = result + ['scripts/' + file]
+ return result
+
+def session_file():
+ result = ['scripts/gnome-dogtail-headless.session']
+ return result
+
+setup (
+ name = 'dogtail',
+ version = '0.9.0',
+ description = """GUI test tool and automation framework that uses Accessibility (a11y) technologies to communicate with desktop applications.""",
+ author = """Zack Cerza <zcerza@redhat.com>,
+Ed Rousseau <rousseau@redhat.com>,
+David Malcolm <dmalcolm@redhat.com>
+Vitezslav Humpa <vhumpa@redhat.com>""",
+ author_email = 'dogtail-list@gnome.org',
+ url = 'http://dogtail.fedorahosted.org/',
+ packages = ['dogtail'],
+ scripts = scripts(),
+ data_files = [
+ ('share/doc/dogtail/examples',
+ examples() ),
+ ('share/doc/dogtail/examples/data',
+ examples_data() ),
+ ('share/doc/dogtail/tests',
+ tests() ),
+ ('share/dogtail/glade', ['sniff/sniff.ui']),
+ ('share/dogtail/icons', sniff_icons() ),
+ ('share/applications', ['sniff/sniff.desktop']),
+ ('share/icons/hicolor/48x48/apps', icons('png')),
+ ('share/icons/hicolor/scalable/apps', icons('svg'))
+ ],
+ cmdclass = {
+ 'bdist_rpm': bdist_rpm
+ }
+)
+
+# vim: sw=4 ts=4 sts=4 noet ai
diff --git a/sniff/icons/button.xpm b/sniff/icons/button.xpm
new file mode 100644
index 00000000000..eca4d627957
--- /dev/null
+++ b/sniff/icons/button.xpm
@@ -0,0 +1,29 @@
+/* XPM */
+static char * button_xpm[] = {
+"21 21 5 1",
+" c None",
+". c #FFFFFF",
+"+ c #000000",
+"@ c #D6D6D6",
+"# c #7B7B7B",
+" ",
+" ",
+" ",
+" ..................+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .@@@@++@@+@@+@@@@#+ ",
+" .@@@+@@+@+@+@@@@@#+ ",
+" .@@@+@@+@++@@@@@@#+ ",
+" .@@@+@@+@+@+@@@@@#+ ",
+" .@@@+@@+@+@@+@@@@#+ ",
+" .@@@@++@@+@@+@@@@#+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .@@@@@@@@@@@@@@@@#+ ",
+" .#################+ ",
+" +++++++++++++++++++ ",
+" ",
+" ",
+" "};
diff --git a/sniff/icons/checkbutton.xpm b/sniff/icons/checkbutton.xpm
new file mode 100644
index 00000000000..314ba97ce9c
--- /dev/null
+++ b/sniff/icons/checkbutton.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *checkbutton_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 6 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++OOOOOOOOOOO+++++",
+"+++++OXXXXXXXXX +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OXXXXXXXX. +++++",
+"+++++OX........ +++++",
+"+++++O +++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++"
+};
diff --git a/sniff/icons/checkmenuitem.xpm b/sniff/icons/checkmenuitem.xpm
new file mode 100644
index 00000000000..197eee36440
--- /dev/null
+++ b/sniff/icons/checkmenuitem.xpm
@@ -0,0 +1,28 @@
+/* XPM */
+static char * checkmenuitem_xpm[] = {
+"21 21 4 1",
+" c None",
+". c #FFFFFF",
+"+ c #7B7B7B",
+"@ c #000000",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ........ ",
+" . +@ ",
+" . +@ ",
+" . +@ ",
+" . +@ @ ",
+" . +++++@ @@ ",
+" .@@@@@@@ @@@@ ",
+" @@@@@ ",
+" @@@@ ",
+" @@@ ",
+" @ @ ",
+" @ ",
+" @ ",
+" "};
diff --git a/sniff/icons/colorselection.xpm b/sniff/icons/colorselection.xpm
new file mode 100644
index 00000000000..0bb0914d0c8
--- /dev/null
+++ b/sniff/icons/colorselection.xpm
@@ -0,0 +1,35 @@
+/* XPM */
+static char *colorselection_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 8 1",
+" c Gray0",
+". c #7b7b7b",
+"X c Blue",
+"o c Green",
+"O c Red",
+"+ c #b3cece",
+"@ c Gray100",
+"# c None",
+/* pixels */
+"#####################",
+"#####################",
+"#####################",
+"#####################",
+"#####################",
+"##................###",
+"##.@@@@@@@@@@@@@@. ##",
+"##.@@@@@@@@@@@@@@. ##",
+"##.@@@@@@@@@@@@@@. ##",
+"##.@@OOOoooXXX@@@. ##",
+"##.@@OOOoooXXX@@@. ##",
+"##.@@OOOoooXXX@@@. ##",
+"##.@@OOOoooXXX@@@. ##",
+"##.@@@@@@@@@@@@@@. ##",
+"##.@@@@@@@@@@@@@@. ##",
+"##.@@@@@@@@@@@@@@. ##",
+"##................ ##",
+"### ##",
+"#####################",
+"#####################",
+"#####################"
+};
diff --git a/sniff/icons/combo.xpm b/sniff/icons/combo.xpm
new file mode 100644
index 00000000000..3b4055b6dff
--- /dev/null
+++ b/sniff/icons/combo.xpm
@@ -0,0 +1,34 @@
+/* XPM */
+static char *combo_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 7 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray87",
+"+ c Gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@",
+" ",
+" +++++++++++++ OOOOO ",
+" +++++++++++++ OOO ",
+" +++++++++++++ O ",
+" ",
+" +++++++++++++ ++++++",
+" +++++++++++++ XXXXX ",
+" +.........+++ XX XX ",
+" +++++++++++++ X X ",
+" +++++++++++++ XXXXX ",
+" +...........+ ",
+" +++++++++++++ +++++ ",
+" +++++++++++++ +XXXX ",
+" +..........++ ..... ",
+" +++++++++++++ +++++ ",
+" +++++++++++++ XXXXX ",
+" +...........+ X X ",
+" +++++++++++++ XX XX ",
+" +++++++++++++ XXXXX ",
+" "
+};
diff --git a/sniff/icons/dialog.xpm b/sniff/icons/dialog.xpm
new file mode 100644
index 00000000000..00473b653ee
--- /dev/null
+++ b/sniff/icons/dialog.xpm
@@ -0,0 +1,38 @@
+/* XPM */
+static char *dialog_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 11 1",
+" c Gray0",
+". c #00007b",
+"X c #7b7b7b",
+"o c Green",
+"O c Cyan",
+"+ c Red",
+"@ c Yellow",
+"# c #d6d6d6",
+"$ c #b3cece",
+"% c Gray100",
+"& c None",
+/* pixels */
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&XXXXXXXXXXXXXXXX&&&",
+"&&X.+@............ &&",
+"&&X.Oo.%%%....# # &&",
+"&&XXXXXXXXXXXXXXXX &&",
+"&&X%%%%%%%%%%%%%%X &&",
+"&&X%%%%%%%%%%%%%%X &&",
+"&&X%%X%XX%XX%XX%%X &&",
+"&&X%%%%%%%%%%%%%%X &&",
+"&&X%%%%%%%%%%%%%%X &&",
+"&&X%%%XXX%%XXX%%%X &&",
+"&&X%%%X %%X %%%X &&",
+"&&X%%%%%%%%%%%%%%X &&",
+"&&XXXXXXXXXXXXXXXX &&",
+"&&& &&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&"
+};
diff --git a/sniff/icons/image.xpm b/sniff/icons/image.xpm
new file mode 100644
index 00000000000..9f353cc4c79
--- /dev/null
+++ b/sniff/icons/image.xpm
@@ -0,0 +1,39 @@
+/* XPM */
+static char *image_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 12 1",
+" c Gray0",
+". c #007b00",
+"X c #7b7b7b",
+"o c Blue",
+"O c Green",
+"+ c Cyan",
+"@ c Yellow",
+"# c #d6d6d6",
+"$ c #b3cece",
+"% c Gray87",
+"& c Gray100",
+"* c None",
+/* pixels */
+"*********************",
+"*********************",
+"*********************",
+"* ",
+"* +++++%%%&+++++++++ ",
+"* +&&&++%%++&+++XX++ ",
+"* +++++++%+++++X@@X+ ",
+"* ++&&++++XX+++X@@X+ ",
+"* +++++++ XXX&++XX++ ",
+"* &+++++XXXXXX++++&+ ",
+"* +++++ +++++ ",
+"* +++++ ###### +++++ ",
+"* +++++ #o##o# +++++ ",
+"* ..... ###### ..... ",
+"* OOOOO ##oo## OOOOO ",
+"* OOOOO ##oo## OOOOO ",
+"* OOOOO OOOOO ",
+"* OOOOOOOOOOOOOOOOOO ",
+"* ",
+"*********************",
+"*********************"
+};
diff --git a/sniff/icons/label.xpm b/sniff/icons/label.xpm
new file mode 100644
index 00000000000..013e13a9632
--- /dev/null
+++ b/sniff/icons/label.xpm
@@ -0,0 +1,30 @@
+/* XPM */
+static char *label_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 3 1",
+" c Gray0",
+". c #b3cece",
+"X c None",
+/* pixels */
+"XXXXXXXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXXXXXXX",
+"XXXXXXXXXX XXXXXXXXX",
+"XXXXXXXXX XXXXXXXXX",
+"XXXXXXXXX XXXXXXXXX",
+"XXXXXXXX XXXXXXXX",
+"XXXXXXXX XXXXXXXX",
+"XXXXXXXX XXXXXXX",
+"XXXXXXX X XXXXXXX",
+"XXXXXXX X XXXXXX",
+"XXXXXX XX XXXXXX",
+"XXXXXX XXXXX",
+"XXXXX XXXXX",
+"XXXXX XXXXX XXXXX",
+"XXXX XXXXX XXXX",
+"XXX XXXXX XXX",
+"XXX XXX XXX",
+"XXXXXXXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXXXXXXX"
+};
diff --git a/sniff/icons/menubar.xpm b/sniff/icons/menubar.xpm
new file mode 100644
index 00000000000..85454839915
--- /dev/null
+++ b/sniff/icons/menubar.xpm
@@ -0,0 +1,34 @@
+/* XPM */
+static char *menubar_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 7 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray87",
+"+ c Gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@+++++++++++++++++++ ",
+"@+XXXXXXXXXXXXXXXXX. ",
+"@+XOOOO.OOOO.OOOO.X. ",
+"@+XOXXX.OXXX.OXXX.X. ",
+"@+XOXXX.OXXX.OXXX.X. ",
+"@+X+++++++...O....X. ",
+"@+X+OOOOOO.XXXXXXXX. ",
+"@+.+O....O.......... ",
+"@ +OOOOOO. ",
+"@@@+O....O. @@@@@@@@@",
+"@@@+OOOOOO. @@@@@@@@@",
+"@@@+O....O. @@@@@@@@@",
+"@@@+OOOOOO. @@@@@@@@@",
+"@@@+O....O. @@@@@@@@@",
+"@@@+OOOOOO. @@@@@@@@@",
+"@@@+....... @@@@@@@@@",
+"@@@@ @@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@"
+};
diff --git a/sniff/icons/menuitem.xpm b/sniff/icons/menuitem.xpm
new file mode 100644
index 00000000000..a1da3fa388e
--- /dev/null
+++ b/sniff/icons/menuitem.xpm
@@ -0,0 +1,26 @@
+/* XPM */
+static char * menuitem_xpm[] = {
+"21 21 2 1",
+" c None",
+". c #000000",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" . ",
+" .. ",
+" .... ",
+" ..... ",
+" .... ",
+" ... ",
+" . . ",
+" . ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" "};
diff --git a/sniff/icons/notebook.xpm b/sniff/icons/notebook.xpm
new file mode 100644
index 00000000000..77ac20d6cb0
--- /dev/null
+++ b/sniff/icons/notebook.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *notebook_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 6 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+OOOOOO.OOOO.OOOO.+++",
+"+OXXXXX.X.X..X.X..+++",
+"+OXXXXX..X.X..X.X.+++",
+"+OXXXXX.X.X..X.X..+++",
+"+OXXXXX.OOOOOOOOOOO +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+OXXXXXXXXXXXXXXXX. +",
+"+O................. +",
+"+ +",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++"
+};
diff --git a/sniff/icons/scrolledwindow.xpm b/sniff/icons/scrolledwindow.xpm
new file mode 100644
index 00000000000..da9951a0ae9
--- /dev/null
+++ b/sniff/icons/scrolledwindow.xpm
@@ -0,0 +1,28 @@
+/* XPM */
+static char * scrolledwindow_xpm[] = {
+"21 21 4 1",
+" c None",
+". c #FFFFFF",
+"+ c #D6D6D6",
+"@ c #000000",
+".....................",
+".+++++++++++++.+++++@",
+".+++++++++++++.++@++@",
+".+++++++++++++.+@@@+@",
+".+++++++++++++.+++++@",
+".+++++++++++++.@@@@@@",
+".+++++++++++++......@",
+".+++++++++++++..++++@",
+".+++++++++++++..++++@",
+".+++++++++++++......@",
+".+++++++++++++.+++++@",
+".+++++++++++++.+@@@+@",
+".+++++++++++++.++@++@",
+".+++++++++++++.+++++@",
+"...............@@@@@@",
+".++++@...@++++@++++++",
+".++@+@.++@+@++@++++++",
+".+@@+@.++@+@@+@++++++",
+".++@+@.++@+@++@++++++",
+".++++@.++@++++@++++++",
+".@@@@@@@@@@@@@@++++++"};
diff --git a/sniff/icons/spinbutton.xpm b/sniff/icons/spinbutton.xpm
new file mode 100644
index 00000000000..f3a313b19dc
--- /dev/null
+++ b/sniff/icons/spinbutton.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *spinbutton_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 6 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+ +",
+"+ OOOOOOOOOOO OOOOO +",
+"+ OOOOOOOOOOO OX X. +",
+"+ OOOOOOOOOOO O . +",
+"+ OOOOOOOOOOO OOOOO +",
+"+ OOOOOOOOOOO O . +",
+"+ OOOOOOOOOOO OX X. +",
+"+ OOOOOOOOOOO ..... +",
+"+ +",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++"
+};
diff --git a/sniff/icons/statusbar.xpm b/sniff/icons/statusbar.xpm
new file mode 100644
index 00000000000..8576d88c6ad
--- /dev/null
+++ b/sniff/icons/statusbar.xpm
@@ -0,0 +1,34 @@
+/* XPM */
+static char *statusbar_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 7 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray87",
+"+ c Gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@+XX.@@@@@@@@@@@@@@",
+"@@@+XX.@@@@@@@@@@@@@@",
+"@@@+XX.@@@@@@@@@@@@@@",
+"@@@+XX.@@@@@@@@@@@@@@",
+"@@@+XX++++++++++++@@@",
+"@@@+XXXXXXXXXXXXXX@@@",
+"@@@+XXXXXXXXXXXXXX@@@",
+"@@@+XX............@@@",
+"@@@+XX.OOOOOOOOOOO@@@",
+"@@@+XX.OOOOOOOOOOO@@@",
+"@@@+XX.OOOOOOOOOOO@@@",
+"@@@+XX++++++++++++@@@",
+"@@@+XXXXXXXXXXXXXX@@@",
+"@@@+XXXXXXXXXXXXXX@@@",
+"@@@ @@@",
+"@@@@@@@@@@@@@@@@@@@@@",
+"@@@@@@@@@@@@@@@@@@@@@"
+};
diff --git a/sniff/icons/table.xpm b/sniff/icons/table.xpm
new file mode 100644
index 00000000000..c04dce03d01
--- /dev/null
+++ b/sniff/icons/table.xpm
@@ -0,0 +1,31 @@
+/* XPM */
+static char *table_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 4 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c Gray100",
+/* pixels */
+"oooooooooo ooooooooo ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"o......... o........ ",
+" ",
+"oooooooooo ooooooooo ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"o......... o........ ",
+" ",
+"oooooooooo ooooooooo ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"oXXXXXXXX. oXXXXXXX. ",
+"o......... o........ ",
+" "
+};
diff --git a/sniff/icons/text.xpm b/sniff/icons/text.xpm
new file mode 100644
index 00000000000..bd143ef9ef9
--- /dev/null
+++ b/sniff/icons/text.xpm
@@ -0,0 +1,32 @@
+/* XPM */
+static char *text_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 5 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c Gray87",
+"O c Gray100",
+/* pixels */
+"....................O",
+". XO",
+". OOOOOOOOOOOOOOOOOXO",
+". OOOOOOO OOOOOOOOOXO",
+". OOOOOOO OOOOOOOOOXO",
+". OO OO o OOo OXO",
+". O.OOO O Oo O Oo XO",
+". OO O OOO O OOOXO",
+". O OOO O OOO O OOOXO",
+". O OOO O OOo O Oo XO",
+". OO O OOo OXO",
+". OOOOOOOOOOOOOOOOOXO",
+". OOOOO OOOOOOOOO XO",
+". OOOOO OO OOO oOXO",
+". OO O oOo OO OOXO",
+". O oOo O oOO O OXO",
+". O OOO O OOO OOXO",
+". O oOo O OOO.OO OOXO",
+". OO OO OOO OOXO",
+".XXXXXXXXXXXXXXXXXXXO",
+"OOOOOOOOOOOOOOOOOOOOO"
+};
diff --git a/sniff/icons/toolbar.xpm b/sniff/icons/toolbar.xpm
new file mode 100644
index 00000000000..d9a5217161e
--- /dev/null
+++ b/sniff/icons/toolbar.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *toolbar_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 6 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+OOOOOOOOOOOOOOOOOOO ",
+"+OXXXXXXXXXXXXXXXXX. ",
+"+OXOOOO OOOO OOOO X. ",
+"+OXOXXX OXXX OXXX X. ",
+"+OXOXXX OXXX OXXX X. ",
+"+OXO O O X. ",
+"+OXXXXXXXXXXXXXXXXX. ",
+"+O.................. ",
+"+ ",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++"
+};
diff --git a/sniff/icons/tree.xpm b/sniff/icons/tree.xpm
new file mode 100644
index 00000000000..0a15524b870
--- /dev/null
+++ b/sniff/icons/tree.xpm
@@ -0,0 +1,34 @@
+/* XPM */
+static char *tree_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 7 1",
+" c Gray0",
+". c #7b7b7b",
+"X c Green",
+"o c Cyan",
+"O c Yellow",
+"+ c #b3cece",
+"@ c None",
+/* pixels */
+"@ @@@@@@@@@@@@@@@@@",
+"@ o @......@@@@@@@@@@",
+"@ @@@@@@@@@@@@@@@@@",
+"@@ @@@@@@@@@@@@@@@@@@",
+"@@ @@ @@@@@@@@@@@@@",
+"@@ O @......@@@@@@",
+"@@ @@ @@@@@@@@@@@@@",
+"@@ @@@ @@@@@@@@@@@@@@",
+"@@ @@@ @@ @@@@@@@@@",
+"@@ @@@ X @......@@",
+"@@ @@@ @@ @@@@@@@@@",
+"@@ @@@ @@@@@@@@@@@@@@",
+"@@ @@@ @@ @@@@@@@@@",
+"@@ @@@ X @......@@",
+"@@ @@@@@@ @@@@@@@@@",
+"@@ @@@@@@@@@@@@@@@@@@",
+"@@ @@ @@@@@@@@@@@@@",
+"@@ O @......@@@@@@",
+"@@@@@ @@@@@@@@@@@@@",
+"@@@@@@ @@@@@@@@@@@@@@",
+"@@@@@@ @@@@@@@@@@@@@@"
+};
diff --git a/sniff/icons/treeitem.xpm b/sniff/icons/treeitem.xpm
new file mode 100644
index 00000000000..2c0bf836970
--- /dev/null
+++ b/sniff/icons/treeitem.xpm
@@ -0,0 +1,35 @@
+/* XPM */
+static char *treeitem_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 8 1",
+" c Gray0",
+". c #7b7b7b",
+"X c Red",
+"o c Yellow",
+"O c #b3cece",
+"+ c Gray87",
+"@ c Gray100",
+"# c None",
+/* pixels */
+"#####################",
+"#####################",
+"#####################",
+"###...###############",
+"## .@ #+++++#########",
+"### ###############",
+"#### ################",
+"#### #...############",
+"#### .o #XXXXXXX####",
+"#### # #XXXXXXX####",
+"#### ################",
+"#### #...############",
+"#### .@ #+++++######",
+"#### # ############",
+"#### ## #############",
+"#### ## #...#########",
+"#### ## .o #+++++###",
+"#### #### #########",
+"#####################",
+"#####################",
+"#####################"
+};
diff --git a/sniff/icons/unknown.xpm b/sniff/icons/unknown.xpm
new file mode 100644
index 00000000000..7ce9bce5738
--- /dev/null
+++ b/sniff/icons/unknown.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *unknown_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 6 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"+++++++++++++++++++++",
+"+++++++++++++++++++++",
+"++OOOOOOOOOOOOOOOO ++",
+"++OXXXXXXXXXXXXXX. ++",
+"++OXXXXX XXXXXX. ++",
+"++OXXXX XXXXX. ++",
+"++OXXX X XXXX. ++",
+"++OXXX XXX XXXX. ++",
+"++OXXXXXXX XXXX. ++",
+"++OXXXXXX XXXXX. ++",
+"++OXXXXXX XXXXXX. ++",
+"++OXXXXXX XXXXXX. ++",
+"++OXXXXXX XXXXXX. ++",
+"++OXXXXXXXXXXXXXX. ++",
+"++OXXXXXX XXXXXX. ++",
+"++OXXXXXX XXXXXX. ++",
+"++OXXXXXXXXXXXXXX. ++",
+"++O............... ++",
+"++ ++",
+"+++++++++++++++++++++",
+"+++++++++++++++++++++"
+};
diff --git a/sniff/icons/viewport.xpm b/sniff/icons/viewport.xpm
new file mode 100644
index 00000000000..71e5e80cf58
--- /dev/null
+++ b/sniff/icons/viewport.xpm
@@ -0,0 +1,34 @@
+/* XPM */
+static char *viewport_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 7 1",
+" c Gray0",
+". c #7b7b7b",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray87",
+"+ c Gray100",
+"@ c None",
+/* pixels */
+"@@@@@@@@@@@@@@@@@@@@@",
+"@...................@",
+"@. +O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@. XXXXXXXXXXXXXXX+O@",
+"@.+++++++++++++++++O@",
+"@.OOOOOOOOOOOOOOOOOO@",
+"@@@@@@@@@@@@@@@@@@@@@"
+};
diff --git a/sniff/icons/vscrollbar.xpm b/sniff/icons/vscrollbar.xpm
new file mode 100644
index 00000000000..0e902b1e7da
--- /dev/null
+++ b/sniff/icons/vscrollbar.xpm
@@ -0,0 +1,33 @@
+/* XPM */
+static char *vscrollbar_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"20 21 6 1",
+" c Gray0",
+". c Gray61",
+"X c #d6d6d6",
+"o c #b3cece",
+"O c Gray100",
+"+ c None",
+/* pixels */
+"++++++ +++++",
+"++++++ ...O...O+++++",
+"++++++ ..OX ..O+++++",
+"++++++ .OXXX .O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ O+++++",
+"++++++ .......O+++++",
+"++++++ OOOOOO O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ OXXXXX O+++++",
+"++++++ O O+++++",
+"++++++ .......O+++++",
+"++++++ OOOOOOOO+++++",
+"++++++ OXXXXX O+++++",
+"++++++ .OXXX .O+++++",
+"++++++ ..OX ..O+++++",
+"++++++ ... ...O+++++",
+"++++++OOOOOOOOO+++++"
+};
diff --git a/sniff/icons/vseparator.xpm b/sniff/icons/vseparator.xpm
new file mode 100644
index 00000000000..3455f74d93b
--- /dev/null
+++ b/sniff/icons/vseparator.xpm
@@ -0,0 +1,31 @@
+/* XPM */
+static char *vseparator_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 4 1",
+" c #7b7b7b",
+". c #b3cece",
+"X c Gray100",
+"o c None",
+/* pixels */
+"ooooooooooooooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"oooooooooo Xooooooooo",
+"ooooooooooooooooooooo"
+};
diff --git a/sniff/icons/window.xpm b/sniff/icons/window.xpm
new file mode 100644
index 00000000000..8407e2836a5
--- /dev/null
+++ b/sniff/icons/window.xpm
@@ -0,0 +1,38 @@
+/* XPM */
+static char *window_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"21 21 11 1",
+" c Gray0",
+". c #00007b",
+"X c #7b7b7b",
+"o c Green",
+"O c Cyan",
+"+ c Red",
+"@ c Yellow",
+"# c #d6d6d6",
+"$ c #b3cece",
+"% c Gray100",
+"& c None",
+/* pixels */
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&",
+"XXXXXXXXXXXXXXXXXXXX&",
+"X.+@...............X ",
+"X.Oo.%%%.%%.....# #X ",
+"XXXXXXXXXXXXXXXXXXXX ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"X#%%%%%%%%%%%%%%%%%X ",
+"XXXXXXXXXXXXXXXXXXXX ",
+"& ",
+"&&&&&&&&&&&&&&&&&&&&&",
+"&&&&&&&&&&&&&&&&&&&&&"
+};
diff --git a/sniff/sniff b/sniff/sniff
new file mode 100755
index 00000000000..48949003aec
--- /dev/null
+++ b/sniff/sniff
@@ -0,0 +1,798 @@
+#!/usr/bin/env python
+# -*- coding: UTF8 -*-
+"""
+http://en.wikipedia.org/wiki/Model-view-controller
+
+The SniffApp class sets up all of sniff's widgets.
+
+Data storage is handled by the SniffModel class.
+There is no SniffView class; we just use a GtkTreeView.
+Data display is handled by the SniffController class.
+"""
+import gi
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
+import sys
+from dogtail.config import config
+
+if config.checkForA11y:
+ from dogtail.utils import checkForA11yInteractively
+ checkForA11yInteractively()
+
+config.logDebugToFile = False
+config.childrenLimit = 100000
+
+import pyatspi
+import Accessibility
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import Gio
+from gi.repository import GdkPixbuf
+from gi.repository import GObject
+from gi.repository import GLib
+
+builder = Gtk.Builder()
+
+
+class SniffApp(object):
+ appName = 'Sniff'
+ appAuthors = ['Zack Cerza <zcerza@redhat.com>',
+ 'David Malcolm <dmalcolm@redhat.com']
+
+ def __init__(self):
+ self.builder = builder
+ import os
+ if os.path.exists('sniff.ui'):
+ self.builder.add_from_file('sniff.ui')
+ else:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/': # in case the path is /bin/sniff
+ path = '/usr'
+ self.builder.add_from_file(path +
+ '/share/dogtail/glade/sniff.ui')
+ self.app = self.builder.get_object(self.appName)
+ try:
+ self.app.set_icon_from_file('../icons/dogtail-head.svg')
+ except Exception:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/':
+ path = '/usr'
+ self.app.set_icon_from_file(os.path.join(path,
+ 'share/icons/hicolor/scalable/apps/dogtail-head.svg'))
+ self.setUpWidgets()
+ self.connectSignals()
+ self.app.show_all()
+ Gtk.main()
+
+ def setUpWidgets(self):
+ self.quit1 = self.builder.get_object('quit1')
+ self.expand_all1 = self.builder.get_object('expand_all1')
+ self.collapse_all1 = self.builder.get_object('collapse_all1')
+ self.about1 = self.builder.get_object('about1')
+ self.refreshMenuItem = self.builder.get_object('refresh1')
+ self.autoRefreshMenuItem = self.builder.get_object('autorefresh')
+ self.setRootMenuItem = self.builder.get_object('setRootMenuItem')
+ self.unsetRootMenuItem = self.builder.get_object('unsetRootMenuItem')
+ self.about = None
+
+ self.tree = SniffController()
+
+ def connectSignals(self):
+ self.app.connect('delete_event', self.quit, self)
+ self.quit1.connect('activate', self.quit, self)
+ self.expand_all1.connect('activate', self.tree.expandAll, True)
+ self.collapse_all1.connect('activate', self.tree.expandAll, False)
+ self.about1.connect('activate', self.showAbout, self)
+ self.refreshMenuItem.connect('activate', self.tree.refresh)
+ self.autoRefreshMenuItem.connect(
+ 'toggled', self.tree.toggleAutoRefresh)
+ self.setRootMenuItem.connect('activate', self.tree.changeRoot, True)
+ self.unsetRootMenuItem.connect('activate', self.tree.changeRoot, False)
+
+ self.setStartupAutoRefresh()
+
+ def setStartupAutoRefresh(self):
+ import os
+ if not os.path.exists('/tmp/sniff_refresh.lock'):
+ self.autoRefreshMenuItem.set_active(True)
+
+ def showAbout(self, *args):
+ if not self.about:
+ self.about = Gtk.AboutDialog()
+ self.about.set_name(self.appName)
+ self.about.set_authors(self.appAuthors)
+ self.about.set_comments('Explore your desktop with Dogtail')
+ self.about.set_website('http://people.redhat.com/zcerza/dogtail/')
+ self.about.connect("response", self.hideAbout)
+ self.about.show_all()
+
+ def hideAbout(self, window, response):
+ if response == Gtk.ResponseType.CANCEL:
+ window.hide()
+
+ def quit(self, *args):
+ Gtk.main_quit()
+
+
+class SniffController(object):
+ invalidBufferCallbackID = None
+
+ def __init__(self):
+ self.builder = builder
+ self.nameTextLabel = self.builder.get_object('nameTextLabel')
+ self.nameTextLabel.set_text('')
+ self.roleNameTextLabel = self.builder.get_object('roleNameTextLabel')
+ self.roleNameTextLabel.set_text('')
+ self.descTextLabel = self.builder.get_object('descTextLabel')
+ self.descTextLabel.set_text('')
+ self.actionsTextLabel = self.builder.get_object('actionsTextLabel')
+ self.actionsTextLabel.set_text('')
+ self.textTextView = self.builder.get_object('textTextView')
+ self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
+ self.textTextView.set_sensitive(False)
+ self.textTextView.get_buffer().set_text('')
+ self.labelerButton = self.builder.get_object('labelerButton')
+ self.labelerButton.set_sensitive(False)
+ self.labeleeButton = self.builder.get_object('labeleeButton')
+ self.labeleeButton.set_sensitive(False)
+ self.stateView = self.builder.get_object('stateTreeView')
+ self.highlight1 = self.builder.get_object('highlight1')
+ self.autorefresh = self.builder.get_object('autorefresh')
+ self.stateModel = StateModel()
+ self.setUpStateView()
+ self.treeView = self.builder.get_object('treeTreeView')
+ self.treeSelection = self.treeView.get_selection()
+ self.treeModel = SniffModel()
+ self.setUpTreeView()
+ self.connectSignals()
+ self.refresh()
+
+ def setUpStateView(self):
+ self.stateView.set_model(self.stateModel)
+ cellRenderer = Gtk.CellRendererText()
+ col = Gtk.TreeViewColumn('Present States', cellRenderer,
+ text=self.stateModel.stateColumn)
+ col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
+ self.stateView.insert_column(col, -1)
+
+ def setUpTreeView(self):
+ self.treeView.set_enable_tree_lines(True)
+
+ self.treeView.set_model(self.treeModel)
+
+ col = Gtk.TreeViewColumn()
+ cellRenderer = Gtk.CellRendererPixbuf()
+ col.pack_start(cellRenderer, expand=False)
+ col.add_attribute(cellRenderer, 'pixbuf', self.treeModel.pixbufColumn)
+
+ cellRenderer = Gtk.CellRendererText()
+ col.pack_end(cellRenderer, expand=False)
+ col.add_attribute(cellRenderer, 'text', self.treeModel.nameColumn)
+
+ col.set_title('Name')
+
+ self.treeView.insert_column(col, -1)
+
+ for column in self.treeView.get_columns():
+ column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
+ column.set_resizable(True)
+ self.treeView.show()
+ path = 0
+ self.treeView.expand_all()
+ #self.rowExpanded(self.treeView, self.treeModel.get_iter(path), path)
+
+ def changeRoot(self, menuItem, toSelected=True, *args):
+ if toSelected:
+ node = self.getSelectedNode()
+ if toSelected and node:
+ self.treeModel.changeRoot(node)
+ elif not toSelected:
+ self.treeModel.reset()
+ else:
+ return
+ self.refresh(refreshModel=False)
+
+ def refresh(self, menuItem=None, refreshModel=True, *args):
+ if refreshModel:
+ self.treeModel.refresh()
+ rootPath = self.treeModel.get_path(self.treeModel.get_iter_first())
+ self.treeView.expand_all()
+ self.treeView.expand_row(rootPath, False)
+
+ def toggleAutoRefresh(self, *args):
+ if self.autorefresh.get_active() is True:
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:children-changed')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:property-change:accessible-name')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:property-change:accessible-state')
+ pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
+ 'object:state-changed')
+ self.refresh()
+ else:
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:children-changed')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:property-change:accessible-name')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:property-change:accessible-state')
+ pyatspi.Registry.deregisterEventListener(
+ self.treeModel.nodeChanged,
+ 'object:state-changed')
+
+ def connectSignals(self):
+ self.labelerButton.connect('clicked', self.showRelationTarget,
+ 'labeler')
+ self.labeleeButton.connect('clicked', self.showRelationTarget,
+ 'labelee')
+ self.treeView.connect('button-press-event', self.buttonPress)
+ self.treeView.connect('key-press-event', self.keyPress)
+ self.treeView.connect('row-expanded', self.rowExpanded, self.treeModel)
+ self.treeView.connect('row-collapsed', self.rowCollapsed)
+ self.treeSelection.connect('changed', self.selectionChanged)
+ self.refresh()
+
+ def selectionChanged(self, treeSelection):
+ node = self.getSelectedNode()
+ if node:
+ self.setUpBottomPane(node)
+ if self.highlight1.get_active() is True:
+ node.blink()
+
+ def getSelectedNode(self):
+ (store, iter) = self.treeView.get_selection().get_selected()
+ if not iter:
+ node = None
+ else:
+ node = self.treeModel.getNode(iter)
+ return node
+
+ def expandAll(self, widget, *args):
+ if args[0] == True:
+ self.treeView.expand_all()
+ elif args[0] == False:
+ self.treeView.collapse_all()
+
+ def rowExpanded(self, treeview, iter, path, *userParams):
+ row = self.treeModel[path]
+ childRows = row.iterchildren()
+ while True:
+ try:
+ childRow = childRows.next()
+ self.treeModel.populateChildren(childRow.iter)
+ except StopIteration:
+ break
+
+ def rowCollapsed(self, treeview, iter, path, *userParams):
+ row = self.treeModel[path]
+ childRows = row.iterchildren()
+ try:
+ while True:
+ childRow = childRows.next()
+ grandChildRows = childRow.iterchildren()
+ try:
+ while True:
+ grandChildRow = grandChildRows.next()
+ self.treeModel.remove(grandChildRow.iter)
+ except StopIteration:
+ pass
+ except StopIteration:
+ pass
+
+ def menuItemActivate(self, menuItem, *userParams):
+ if len(userParams) < 2:
+ return
+ method = userParams[0]
+ arg = userParams[1]
+ method(arg)
+
+ def keyPress(self, widget, event, *userParams):
+ if event.keyval == Gdk.KEY_Return:
+ path = self.treeSelection.get_selected_rows()[1][0]
+ if self.treeView.row_expanded(path):
+ self.treeView.collapse_row(path)
+ else:
+ self.treeView.expand_row(path, False)
+ return False
+
+ def buttonPress(self, widget, event, *userParams):
+ try:
+ path, treeViewCol, relX, relY = \
+ self.treeView.get_path_at_pos(int(event.x),
+ int(event.y))
+ except TypeError:
+ return
+ node = self.treeModel.getNode(self.treeModel.get_iter(path))
+ if node == None:
+ return
+
+ if event.button == 3:
+ self.menu = Gtk.Menu()
+ menuItem = None
+ if node.actions:
+ for action in node.actions.keys():
+ menuItem = Gtk.MenuItem(action.capitalize())
+ menuItem.connect(
+ 'activate', self.menuItemActivate, node.doActionNamed, action)
+ menuItem.show()
+ self.menu.append(menuItem)
+ if not menuItem:
+ return
+ self.menu.show_all()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+
+ def showRelationTarget(self, button, relation, *args):
+ target = getattr(self.getSelectedNode(), relation)
+ if not target:
+ return
+ try:
+ target.blink()
+ except:
+ import traceback
+ traceback.print_exc()
+
+ def setUpBottomPane(self, node):
+ """Generic code for setting up the table under the TreeView"""
+ if node == None:
+ return
+ self.nameTextLabel.set_text(node.name)
+ self.roleNameTextLabel.set_text(node.roleName)
+ self.descTextLabel.set_text(node.description)
+ str = ''
+ if node.actions:
+ str = ' '.join(node.actions.keys())
+ self.actionsTextLabel.set_text(str)
+
+ # Have we connected this signal yet?
+ # If so, disconnect it before proceeding.
+ if self.textTextViewBufferCallbackID != self.invalidBufferCallbackID:
+ self.textTextView.get_buffer().disconnect(
+ self.textTextViewBufferCallbackID)
+ self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
+
+ if node.text is not None:
+ buffer = self.textTextView.get_buffer()
+ buffer.set_text(node.text)
+ try:
+ node.queryEditableText()
+ # Remember the handler ID of this connection.
+ self.textTextView.set_sensitive(True)
+ self.textTextViewBufferCallbackID = \
+ buffer.connect('changed', self.changeText, node)
+ except NotImplementedError:
+ self.textTextView.set_sensitive(False)
+ else:
+ self.textTextView.get_buffer().set_text('')
+ self.textTextView.set_sensitive(False)
+
+ if node.labeler and not node.labeler.dead:
+ self.labelerButton.set_sensitive(True)
+ self.labelerButton.show()
+ # elif node.labeler and node.labeler.dead:
+ # print "labeler is dead", node.labeler
+ else:
+ self.labelerButton.set_sensitive(False)
+ self.labelerButton.hide()
+ if node.labelee and not node.labelee.dead:
+ self.labeleeButton.set_sensitive(True)
+ self.labeleeButton.show()
+ # elif node.labelee and node.labelee.dead:
+ # print "labelee is dead", node.labelee
+ else:
+ self.labeleeButton.set_sensitive(False)
+ self.labeleeButton.hide()
+
+ self.stateModel.setNode(node)
+
+ def changeText(self, textBuffer, node):
+ if node == None:
+ return
+ node.text = textBuffer.get_text(textBuffer.get_start_iter(),
+ textBuffer.get_end_iter())
+
+
+class SniffModel(Gtk.TreeStore):
+ nodeColumn = 0
+ nameColumn = 1
+ pixbufColumn = 2
+ eventQueue = []
+ cache = {}
+
+ def __init__(self):
+ self.builder = builder
+ #self.autorefresh = self.builder.get_object('autorefresh')
+ Gtk.TreeStore.__init__(self, GObject.TYPE_PYOBJECT,
+ GObject.TYPE_STRING, GdkPixbuf.Pixbuf)
+ root = pyatspi.Registry.getDesktop(0)
+ self.rootNode = self.initialRootNode = root
+ self.appendAndPopulate(None, self.rootNode)
+
+ def __contains__(self, item):
+ from dogtail.tree import Node
+ if isinstance(item, Node):
+ if item in self.cache:
+ row = self.cache[item]
+ # If row is None, we need to call getPath() to be sure
+ if not row:
+ path = self.getPath(item)
+ return path is not None
+ elif row in self:
+ return True
+ return False
+ elif isinstance(item, Gtk.TreeIter):
+ return self.iter_is_valid(item)
+ elif isinstance(item, list) or isinstance(item, tuple):
+ try:
+ iter = self.get_iter(item)
+ except ValueError:
+ return False
+ return iter in self
+ elif isinstance(item, Gtk.TreeRowReference):
+ return item.valid() and item.get_path() in self
+ else:
+ raise TypeError
+
+ def changeRoot(self, node):
+ self.rootNode = node
+ self.refresh()
+
+ def reset(self):
+ self.rootNode = self.initialRootNode
+ self.refresh()
+
+ def refresh(self):
+ self.cache.clear()
+ self.clear()
+ self.appendAndPopulate(None, self.rootNode)
+
+ def append(self, parentIter, node):
+ if node:
+ self.cache[node] = None
+ pb = self.getPixbufForNode(node)
+ return Gtk.TreeStore.append(self, parentIter, (node, node.name, pb))
+
+ def remove(self, iter):
+ node = self.getNode(iter)
+ try:
+ del self.cache[node]
+ finally:
+ return Gtk.TreeStore.remove(self, iter)
+
+ def populateChildren(self, iter):
+ if not iter in self:
+ return False
+ result = True
+ node = self.getNode(iter)
+ try:
+ for child in node.children:
+ if child in self:
+ continue
+ result = result and self.append(iter, child)
+ except GLib.GError as e:
+ if 'name :1.0 was not provided' in e.message:
+ print("Dogtail: warning: omiting possibly broken at-spi application record")
+ return result
+
+ def appendAndPopulate(self, iter, node):
+ childIter = self.append(iter, node)
+ return self.populateChildren(childIter)
+
+ def getNode(self, iter):
+ if not iter in self:
+ return None
+ return self.get_value(iter, self.nodeColumn)
+
+ def getPath(self, node):
+ if not node:
+ raise ValueError
+ try:
+ indexInParent = node.indexInParent
+ except LookupError:
+ return None
+ root = pyatspi.Registry.getDesktop(0)
+ row = self.cache.get(node, None)
+ path = []
+ needParent = True
+ if row:
+ if row in self:
+ path = row.get_path()
+ else:
+ del self.cache[node]
+ elif node == self.rootNode:
+ indexInParent = 0
+ needParent = False
+ elif node.role == pyatspi.ROLE_APPLICATION or node.roleName == \
+ 'application':
+ path = [0]
+ indexInParent = list(root.children).index(node)
+ needParent = False
+ elif not node.parent:
+ return None
+ elif (0 <= indexInParent <= (len(node.parent) - 1)) and \
+ node.parent[indexInParent] != node:
+ return None
+ siblings = node.parent.children
+ sibIndex = siblings.index(node)
+ try:
+ if siblings[sibIndex] != node:
+ return None
+ else:
+ indexInParent = sibIndex
+ except ValueError:
+ return None
+ if type(path) == list:
+ if needParent:
+ parentPath = self.getPath(node.parent)
+ if parentPath is None:
+ return None
+ else:
+ path = list(parentPath)
+ path.append(indexInParent)
+
+ path = tuple(path)
+ try:
+ nodeByPath = self.getNode(self.get_iter(path))
+ if node != nodeByPath:
+ # print "%s is not %s!" % (node, nodeByPath)
+ return None
+ except ValueError:
+ # print "I smell a bug in %s..." % node.getApplication()
+ return None
+
+ #self.cache[node] = Gtk.TreeRowReference(self, path)
+ return path
+
+ def processEvents(self):
+ if not len(self.eventQueue):
+ return
+ queueCopy = self.eventQueue[:]
+ for event in queueCopy:
+ self.processChangedNode(event)
+ self.eventQueue.remove(event)
+ return False
+
+ def nodeChanged(self, event):
+ # if self.autorefresh.get_active() is False: return
+ node = event.source
+ if not node or not node in self:
+ return
+ app = event.host_application
+ if app and app.name == 'sniff':
+ return
+ self.eventQueue.append(event)
+ GObject.idle_add(self.processEvents)
+
+ def processChangedNode(self, event):
+ node = event.source
+ if not node or not node in self:
+ return
+ path = self.getPath(node)
+ try:
+ iter = self.get_iter(path)
+ except (ValueError, TypeError):
+ return
+ if event.type.major == "property-change":
+ if event.type.minor == "accessible-name":
+ node = self.getNode(iter)
+ self.set_value(iter, self.nameColumn, node.name)
+ elif event.type.minor == "accessible-state":
+ print str(event)
+ elif event.type.major == "state-changed":
+ print str(event)
+ elif event.type.major == "children-changed":
+ if event.type.minor == 'add':
+ for child in node.children:
+ if not child in self:
+ if len(child) > 0:
+ self.appendAndPopulate(iter, child)
+ else:
+ # If it has no children now, give it a sec
+ # to come up with some.
+ GObject.timeout_add(1000,
+ self.__addNodeCB, iter, child)
+ elif event.type.minor == 'remove':
+ self.__removeNodeCB(iter, node, path)
+
+ def __addNodeCB(self, iter, parent):
+ self.appendAndPopulate(iter, parent)
+ return False
+
+ def __removeNodeCB(self, iter, parent, path):
+ childRow = self.iter_children(iter)
+ while childRow is not None:
+ node = self.getNode(childRow)
+ if node is None:
+ break
+ if node and self.getNode(childRow) not in parent:
+ self.remove(childRow)
+ else:
+ childRow = self.iter_next(childRow)
+
+ def __populateCB(self, iter):
+ self.populateChildren(iter)
+ return False
+
+ def getPixbufForNode(self, node):
+ theme = Gtk.IconTheme().get_default()
+ try:
+ if node.role == pyatspi.ROLE_APPLICATION:
+ # FIXME: Use the pixbuf from libwcnk (if available):
+ # wnckApp = Application(node).getWnckApplication()
+ # if wnckApp
+ try:
+ return theme.load_icon(node.name, 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except GObject.GError:
+ try:
+ return theme.load_icon(node.name.lower(), 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except GObject.GError:
+ return None
+ elif node.parent:
+ return iconForRole[node.role]
+ else:
+ return theme.load_icon("user-desktop", 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+ except Exception:
+ return theme.load_icon("dialog-error", 24,
+ Gtk.IconLookupFlags.USE_BUILTIN)
+
+
+class StateModel(Gtk.ListStore):
+ stateColumn = 0
+ statesSupported = ['checked', 'focusable', 'focused', 'sensitive',
+ 'showing']
+
+ def __init__(self):
+ Gtk.ListStore.__init__(self, GObject.TYPE_STRING)
+
+ def setNode(self, node):
+ self.clear()
+ for stateName in self.statesSupported:
+ if getattr(node, stateName) is True:
+ self.append((stateName.capitalize(),))
+
+
+def loadIcon(iconName):
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file('icons/' + iconName)
+ except GObject.GError:
+ import os
+ path = os.path.abspath(
+ os.path.join(__file__, os.path.pardir, os.path.pardir))
+ if path is '/':
+ path = '/usr'
+ iconName = os.path.join(path, 'share/dogtail/icons/', iconName)
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(iconName)
+ return pixbuf
+
+button_xpm = loadIcon("button.xpm")
+checkbutton_xpm = loadIcon("checkbutton.xpm")
+checkmenuitem_xpm = loadIcon("checkmenuitem.xpm")
+colorselection_xpm = loadIcon("colorselection.xpm")
+combo_xpm = loadIcon("combo.xpm")
+dialog_xpm = loadIcon("dialog.xpm")
+image_xpm = loadIcon("image.xpm")
+label_xpm = loadIcon("label.xpm")
+menubar_xpm = loadIcon("menubar.xpm")
+menuitem_xpm = loadIcon("menuitem.xpm")
+notebook_xpm = loadIcon("notebook.xpm")
+scrolledwindow_xpm = loadIcon("scrolledwindow.xpm")
+spinbutton_xpm = loadIcon("spinbutton.xpm")
+statusbar_xpm = loadIcon("statusbar.xpm")
+table_xpm = loadIcon("table.xpm")
+text_xpm = loadIcon("text.xpm")
+toolbar_xpm = loadIcon("toolbar.xpm")
+tree_xpm = loadIcon("tree.xpm")
+treeitem_xpm = loadIcon("treeitem.xpm")
+unknown_xpm = loadIcon("unknown.xpm")
+viewport_xpm = loadIcon("viewport.xpm")
+vscrollbar_xpm = loadIcon("vscrollbar.xpm")
+vseparator_xpm = loadIcon("vseparator.xpm")
+window_xpm = loadIcon("window.xpm")
+
+iconForRole = {
+ pyatspi.ROLE_INVALID: None,
+ # pyatspi doesn't have the following... not even sure if it exists
+ # anywhere.
+ # atspi.SPI_ROLE_ACCEL_LABEL : label_xpm,
+ pyatspi.ROLE_ALERT: None,
+ pyatspi.ROLE_ANIMATION: None,
+ pyatspi.ROLE_ARROW: None,
+ pyatspi.ROLE_CALENDAR: None,
+ pyatspi.ROLE_CANVAS: None,
+ pyatspi.ROLE_CHECK_BOX: checkbutton_xpm,
+ pyatspi.ROLE_CHECK_MENU_ITEM: checkmenuitem_xpm,
+ pyatspi.ROLE_COLOR_CHOOSER: colorselection_xpm,
+ pyatspi.ROLE_COLUMN_HEADER: None,
+ pyatspi.ROLE_COMBO_BOX: combo_xpm,
+ pyatspi.ROLE_DATE_EDITOR: None,
+ pyatspi.ROLE_DESKTOP_ICON: None,
+ pyatspi.ROLE_DESKTOP_FRAME: None,
+ pyatspi.ROLE_DIAL: None,
+ pyatspi.ROLE_DIALOG: dialog_xpm,
+ pyatspi.ROLE_DIRECTORY_PANE: None,
+ pyatspi.ROLE_DRAWING_AREA: None,
+ pyatspi.ROLE_FILE_CHOOSER: None,
+ pyatspi.ROLE_FILLER: None,
+ pyatspi.ROLE_FONT_CHOOSER: None,
+ pyatspi.ROLE_FRAME: window_xpm,
+ pyatspi.ROLE_GLASS_PANE: None,
+ pyatspi.ROLE_HTML_CONTAINER: None,
+ pyatspi.ROLE_ICON: image_xpm,
+ pyatspi.ROLE_IMAGE: image_xpm,
+ pyatspi.ROLE_INTERNAL_FRAME: None,
+ pyatspi.ROLE_LABEL: label_xpm,
+ pyatspi.ROLE_LAYERED_PANE: viewport_xpm,
+ pyatspi.ROLE_LIST: None,
+ pyatspi.ROLE_LIST_ITEM: None,
+ pyatspi.ROLE_MENU: menuitem_xpm,
+ pyatspi.ROLE_MENU_BAR: menubar_xpm,
+ pyatspi.ROLE_MENU_ITEM: menuitem_xpm,
+ pyatspi.ROLE_OPTION_PANE: None,
+ pyatspi.ROLE_PAGE_TAB: notebook_xpm,
+ pyatspi.ROLE_PAGE_TAB_LIST: notebook_xpm,
+ pyatspi.ROLE_PANEL: viewport_xpm,
+ pyatspi.ROLE_PASSWORD_TEXT: None,
+ pyatspi.ROLE_POPUP_MENU: None,
+ pyatspi.ROLE_PROGRESS_BAR: None,
+ pyatspi.ROLE_PUSH_BUTTON: button_xpm,
+ pyatspi.ROLE_RADIO_BUTTON: None,
+ pyatspi.ROLE_RADIO_MENU_ITEM: None,
+ pyatspi.ROLE_ROOT_PANE: viewport_xpm,
+ pyatspi.ROLE_ROW_HEADER: None,
+ pyatspi.ROLE_SCROLL_BAR: vscrollbar_xpm,
+ pyatspi.ROLE_SCROLL_PANE: scrolledwindow_xpm,
+ pyatspi.ROLE_SEPARATOR: vseparator_xpm,
+ pyatspi.ROLE_SLIDER: None,
+ pyatspi.ROLE_SPIN_BUTTON: spinbutton_xpm,
+ pyatspi.ROLE_SPLIT_PANE: None,
+ pyatspi.ROLE_STATUS_BAR: statusbar_xpm,
+ pyatspi.ROLE_TABLE: table_xpm,
+ pyatspi.ROLE_TABLE_CELL: treeitem_xpm,
+ pyatspi.ROLE_TABLE_COLUMN_HEADER: None,
+ pyatspi.ROLE_TABLE_ROW_HEADER: None,
+ pyatspi.ROLE_TEAROFF_MENU_ITEM: None,
+ pyatspi.ROLE_TERMINAL: None,
+ pyatspi.ROLE_TEXT: text_xpm,
+ pyatspi.ROLE_TOGGLE_BUTTON: None,
+ pyatspi.ROLE_TOOL_BAR: toolbar_xpm,
+ pyatspi.ROLE_TOOL_TIP: None,
+ pyatspi.ROLE_TREE: tree_xpm,
+ pyatspi.ROLE_TREE_TABLE: tree_xpm,
+ pyatspi.ROLE_UNKNOWN: unknown_xpm,
+ pyatspi.ROLE_VIEWPORT: viewport_xpm,
+ pyatspi.ROLE_WINDOW: window_xpm,
+ pyatspi.ROLE_EXTENDED: None,
+ pyatspi.ROLE_HEADER: None,
+ pyatspi.ROLE_FOOTER: None,
+ pyatspi.ROLE_PARAGRAPH: None,
+ pyatspi.ROLE_RULER: None,
+ pyatspi.ROLE_APPLICATION: None,
+ pyatspi.ROLE_AUTOCOMPLETE: None,
+ pyatspi.ROLE_EDITBAR: None,
+ pyatspi.ROLE_EMBEDDED: None,
+ pyatspi.ROLE_LAST_DEFINED: None}
+
+
+def main():
+ from dogtail.utils import Lock
+ # We need this to prohibit sniff making(and removing on exit)
+ # sniff_refresh lock when importing Node
+ sniff_running = Lock(lockname='sniff_running.lock', randomize=False)
+ try:
+ sniff_running.lock()
+ except OSError:
+ pass
+ sniff = SniffApp()
+
+if __name__ == '__main__':
+ main()
diff --git a/sniff/sniff.desktop b/sniff/sniff.desktop
new file mode 100644
index 00000000000..0835fb69e89
--- /dev/null
+++ b/sniff/sniff.desktop
@@ -0,0 +1,14 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=AT-SPI Browser
+Name[en_CA]=AT-SPI Browser
+Name[en_GB]=AT-SPI Browser
+Comment=Browse your Assistive Technology-enabled desktop
+Comment[en_CA]=Browse your Assistive Technology-enabled desktop
+Comment[en_GB]=Browse your Assistive Technology-enabled desktop
+Exec=sniff
+Terminal=false
+Type=Application
+Icon=dogtail-head
+StartupNotify=true
+Categories=Application;Development;
diff --git a/sniff/sniff.glade b/sniff/sniff.glade
new file mode 100644
index 00000000000..22dd1b0f9f0
--- /dev/null
+++ b/sniff/sniff.glade
@@ -0,0 +1,676 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkWindow" id="Sniff">
+ <property name="width_request">325</property>
+ <property name="height_request">475</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">AT-SPI Browser</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="sniff1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Sniff</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="sniff1_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="quit1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_EXIT_ITEM</property>
+ <signal name="activate" handler="on_quit1_activate" last_modification_time="Tue, 26 Jul 2005 14:21:44 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="view1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_VIEW_TREE</property>
+
+ <child>
+ <widget class="GtkMenu" id="view1_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="refresh1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Refresh</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_refresh1_activate" last_modification_time="Wed, 27 Jul 2005 16:21:27 GMT"/>
+ <accelerator key="R" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-refresh</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="setRootMenuItem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Set Root</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_setRootMenuItem_activate" last_modification_time="Tue, 11 Dec 2007 20:54:23 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="unsetRootMenuItem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Unset Root</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_unsetRootMenuItem_activate" last_modification_time="Tue, 11 Dec 2007 21:18:55 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator3">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="expand_all1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Expand All</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_expand_all1_activate" last_modification_time="Wed, 27 Jul 2005 18:41:34 GMT"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="collapse_all1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Collapse All</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_collapse_all1_activate" last_modification_time="Wed, 27 Jul 2005 18:40:57 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="help1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_HELP_TREE</property>
+
+ <child>
+ <widget class="GtkMenu" id="help1_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="about1">
+ <property name="visible">True</property>
+ <property name="stock_item">GNOMEUIINFO_MENU_ABOUT_ITEM</property>
+ <signal name="activate" handler="on_about1_activate" last_modification_time="Tue, 26 Jul 2005 14:21:44 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">350</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeTreeView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="roleNameLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Role Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="descLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Description:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="nameTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">name</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="roleNameTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">roleName</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="descTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">description</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="actionsLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Actions:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="actionsTextLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">actions</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="tab1Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Basics</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">1</property>
+ <property name="n_columns">1</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">0</property>
+ <property name="column_spacing">0</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTextView" id="textTextView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="overwrite">False</property>
+ <property name="accepts_tab">True</property>
+ <property name="justification">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap_mode">GTK_WRAP_NONE</property>
+ <property name="cursor_visible">True</property>
+ <property name="pixels_above_lines">0</property>
+ <property name="pixels_below_lines">0</property>
+ <property name="pixels_inside_wrap">0</property>
+ <property name="left_margin">0</property>
+ <property name="right_margin">0</property>
+ <property name="indent">0</property>
+ <property name="text" translatable="yes"></property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">expand|shrink|fill</property>
+ <property name="y_options">expand|shrink|fill</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="tab2Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Text</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkButton" id="labelerButton">
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Labeler</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="labeleeButton">
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Labelee</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="tab3Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Relations</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="stateTreeView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="tab4Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">States</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">False</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/sniff/sniff.ui b/sniff/sniff.ui
new file mode 100644
index 00000000000..1594dc3a3ef
--- /dev/null
+++ b/sniff/sniff.ui
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkWindow" id="Sniff">
+ <property name="width_request">325</property>
+ <property name="height_request">475</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">AT-SPI Browser</property>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Sniff</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="quit1">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Actions</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="expand_all1">
+ <property name="label" translatable="yes">Expand All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="collapse_all1">
+ <property name="label" translatable="yes">Collapse All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image2</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="refresh1">
+ <property name="label">Refresh</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image7</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="setRootMenuItem">
+ <property name="label" translatable="yes">Set As Root</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image5</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="unsetRootMenuItem">
+ <property name="label" translatable="yes">Unset As Root Item</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image6</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="highlight1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Highlight Items</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="autorefresh">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Auto Refresh</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Help</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="about1">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">350</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeTreeView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="nameLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Name:</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="roleNameLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Role Name:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="descLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Description:</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="nameTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">name</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="roleNameTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">roleName</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="descTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">description</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="actionsLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Actions:</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="actionsTextLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">actions</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab1Label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Basics</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTextView" id="textTextView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab2Label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Text</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="labelerButton">
+ <property name="label" translatable="yes">Labeler</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="labeleeButton">
+ <property name="label" translatable="yes">Labelee</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab3Label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Relations</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="stateTreeView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab4Label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">States</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-indent</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-unindent</property>
+ </object>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-unindent</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-unindent</property>
+ </object>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-goto-top</property>
+ </object>
+ <object class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-goto-bottom</property>
+ </object>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-refresh</property>
+ </object>
+</interface>
diff --git a/tests/gtkdemotest.py b/tests/gtkdemotest.py
new file mode 100644
index 00000000000..2caa0b4824c
--- /dev/null
+++ b/tests/gtkdemotest.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+import unittest
+
+
+class GtkDemoTest(unittest.TestCase):
+ """
+ TestCase subclass which handles bringing up and shutting down gtk-demo as a fixture. Used for writing other test cases.
+ """
+
+ def setUp(self):
+ import dogtail.config
+ dogtail.config.config.logDebugToStdOut = True
+ dogtail.config.config.logDebugToFile = False
+ import dogtail.utils
+ self.pid = dogtail.utils.run('gtk3-demo')
+ self.app = dogtail.tree.root.application('gtk3-demo')
+
+ def tearDown(self):
+ import os
+ import signal
+ import time
+ os.kill(self.pid, signal.SIGKILL)
+ # Sleep just enough to let the app actually die.
+ # AT-SPI doesn't like being hammered too fast.
+ time.sleep(0.5)
+
+ def runDemo(self, demoName):
+ """
+ Click on the named demo within the gtk-demo app.
+ """
+ tree = self.app.child(roleName="tree table")
+ tree.child(demoName).doActionNamed('activate')
+
+
+def trap_stdout(function, args=None):
+ """
+ Grab stdout output during function execution
+ """
+
+ import sys
+ from StringIO import StringIO
+
+ saved_stdout = sys.stdout
+ try:
+ out = StringIO()
+ sys.stdout = out
+ if type(args) is dict:
+ function(**args)
+ elif args:
+ function(args)
+ else:
+ function()
+ output = out.getvalue().strip()
+ finally:
+ sys.stdout = saved_stdout
+ return output
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 00000000000..7a3a44f46e1
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Unit tests for the dogtail.config class
+"""
+
+import unittest
+import dogtail.config
+
+
+class TestConfiguration(unittest.TestCase):
+
+ def test_get_set_all_properties(self):
+ for option in dogtail.config.config.defaults.keys():
+ print("Setting config.%s property" % option)
+ value = ''
+ if 'Dir' in option:
+ value = '/tmp/dogtail/' # Special value for dir-related properties
+ dogtail.config.config.__setattr__(option, value)
+ self.assertEquals(dogtail.config.config.__getattr__(option), value)
+
+ def test_default_directories_created(self):
+ import os.path
+ self.assertEquals(
+ os.path.isdir(dogtail.config.config.scratchDir), True)
+ self.assertEquals(os.path.isdir(dogtail.config.config.logDir), True)
+ self.assertEquals(os.path.isdir(dogtail.config.config.dataDir), True)
+
+ def test_set(self):
+ self.assertRaises(
+ AttributeError, setattr, dogtail.config.config, 'nosuchoption', 42)
+
+ def test_get(self):
+ self.assertRaises(
+ AttributeError, getattr, dogtail.config.config, 'nosuchoption')
+
+ def helper_create_directory_and_set_option(self, path, property_name):
+ import os.path
+ if os.path.isdir(path):
+ import shutil
+ shutil.rmtree(path)
+ dogtail.config.config.__setattr__(property_name, path)
+ self.assertEquals(os.path.isdir(path), True)
+
+ def test_create_scratch_directory(self):
+ new_folder = "/tmp/dt"
+ self.helper_create_directory_and_set_option(new_folder, 'scratchDir')
+
+ def test_create_data_directory(self):
+ new_folder = "/tmp/dt_data"
+ self.helper_create_directory_and_set_option(new_folder, 'dataDir')
+
+ def test_create_log_directory(self):
+ new_folder = "/tmp/dt_log"
+ self.helper_create_directory_and_set_option(new_folder, 'logDir')
+
+ def test_load(self):
+ dogtail.config.config.load({'actionDelay': 2.0})
+ self.assertEquals(dogtail.config.config.actionDelay, 2.0)
+
+ def test_reset(self):
+ default_actionDelay = dogtail.config.config.defaults['actionDelay']
+ dogtail.config.config.actionDelay = 2.0
+ dogtail.config.config.reset()
+ self.assertEquals(
+ dogtail.config.config.actionDelay, default_actionDelay)
diff --git a/tests/test_logging.py b/tests/test_logging.py
new file mode 100644
index 00000000000..848c736a8d2
--- /dev/null
+++ b/tests/test_logging.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Unit tests for the dogtail.logging package
+"""
+import unittest
+import dogtail.tree
+from gtkdemotest import trap_stdout
+
+
+class TestLogging(unittest.TestCase):
+
+ def setUp(self):
+ self.old_log_dir = dogtail.config.config.logDir
+
+ def tearDown(self):
+ dogtail.config.config.logDebugToFile = False
+ dogtail.config.config.logDir = self.old_log_dir
+
+ def test_entryStamp_is_not_empty(self):
+ ts = dogtail.logging.TimeStamp()
+ self.assertEquals(len(ts.entryStamp()) > 0, True)
+
+ def test_correct_error_if_log_dir_does_not_exist(self):
+ import shutil
+ shutil.rmtree(dogtail.config.config.logDir)
+ self.assertRaises(IOError, dogtail.logging.Logger, "log", file=True)
+
+ def test_unique_name(self):
+ logger1 = dogtail.logging.Logger("log", file=True)
+ logger1.createFile()
+ logger2 = dogtail.logging.Logger("log", file=True)
+ logger2.createFile()
+ logger3 = dogtail.logging.Logger("log", file=True)
+ self.assertNotEquals(logger1.fileName, logger2.fileName)
+ self.assertNotEquals(logger2.fileName, logger3.fileName)
+
+ def test_no_new_line_to_file(self):
+ dogtail.config.config.logDebugToFile = True
+ logger = dogtail.logging.Logger("log", file=True, stdOut=False)
+ logger.log("hello world", newline=False)
+ self.assertTrue("hello world " in open(logger.fileName, 'r').read())
+
+ def test_no_new_line_to_stdout(self):
+ dogtail.config.config.logDebugToFile = False
+ logger = dogtail.logging.Logger("log", file=False, stdOut=True)
+ output = trap_stdout(
+ logger.log, {'message': 'hello world', 'newline': False})
+ self.assertEquals(output, "hello world")
+
+ def test_no_new_line_to_both_file_and_stdout(self):
+ dogtail.config.config.logDebugToFile = True
+ logger = dogtail.logging.Logger("log", file=True, stdOut=True)
+ output = trap_stdout(
+ logger.log, {'message': 'hello world', 'newline': False})
+ self.assertTrue("hello world" in output)
+ self.assertTrue("hello world " in open(logger.fileName, 'r').read())
+
+ def test_empty_script_name(self):
+ dogtail.config.config.scriptName = None
+ logger = dogtail.logging.Logger("log", file=True, stdOut=True)
+ self.assertTrue(logger.fileName, "log")
+
+ def test_force_to_file(self):
+ dogtail.config.config.logDebugToFile = False
+ logger = dogtail.logging.Logger("log", file=True, stdOut=False)
+ logger.log("hello world", force=True)
+ self.assertTrue("hello world" in open(logger.fileName, 'r').read())
+
+ def test_results_logger_correct_dict(self):
+ logger = dogtail.logging.ResultsLogger("log")
+ output = trap_stdout(logger.log, {'entry': {'a': '1'}})
+ self.assertEquals('a: 1' in output, True)
+
+ def test_results_logger_incorrect_dict(self):
+ logger = dogtail.logging.ResultsLogger("log")
+ self.assertRaises(ValueError, logger.log, "not a dict")
diff --git a/tests/test_node.py b/tests/test_node.py
new file mode 100644
index 00000000000..1353925e501
--- /dev/null
+++ b/tests/test_node.py
@@ -0,0 +1,570 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Unit tests for the dogtail.Node class
+
+Notes on pyunit (the "unittest" module):
+
+Test classes are written as subclass of unittest.TestCase.
+A test is a method of such a class, beginning with the string "test"
+
+unittest.main() will run all such methods. Use "-v" to get feedback on which tests are being run. Tests are run in alphabetical order; all failure reports are gathered at the end.
+
+setUp and tearDown are "magic" methods, called before and after each such
+test method is run.
+"""
+__author__ = "Dave Malcolm <dmalcolm@redhat.com>"
+
+import unittest
+import dogtail.tree
+import dogtail.predicate
+import dogtail.config
+dogtail.config.config.logDebugToFile = False
+import pyatspi
+from nose.tools import nottest
+from gtkdemotest import GtkDemoTest, trap_stdout
+
+
+class TestNodeAttributes(GtkDemoTest):
+
+ """
+ Unit tests for the the various synthesized attributes of a Node
+ """
+
+ def testGetBogus(self):
+ "Getting a non-existant attribute should raise an attribute error"
+ self.assertRaises(
+ AttributeError, getattr, self.app, "thisIsNotAnAttribute")
+
+ # FIXME: should setattr for a non-existant attr be allowed?
+
+ # 'name' (read-only string):
+ def testGetName(self):
+ """
+ Node.name of the gtk-demo app should be "gtk-demo"
+ """
+ self.assertEquals(self.app.name, 'gtk3-demo')
+
+ self.assertEquals(dogtail.tree.root.name, 'main')
+
+ def testSetName(self):
+ "Node.name should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "name", "hello world")
+
+ # 'roleName' (read-only string):
+ def testGetRoleName(self):
+ """
+ roleName of the gtk-demo app should be "application"
+ """
+ self.assertEquals(self.app.roleName, 'application')
+
+ def testSetRoleName(self):
+ """Node.roleName should be read-only"""
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "roleName", "hello world")
+
+ # 'role' (read-only atspi role enum):
+ def testGetRole(self):
+ """Node.role for a gtk-demo app should be SPI_ROLE_APPLICATION"""
+ self.assertEquals(self.app.role, dogtail.tree.pyatspi.ROLE_APPLICATION)
+
+ def testSetRole(self):
+ """Node.role should be read-only"""
+ # FIXME should be AttributeError?
+ self.assertRaises(
+ RuntimeError, self.app.__setattr__, "role", pyatspi.Atspi.Role(1))
+
+ # 'description' (read-only string):
+ def testGetDescription(self):
+ # FIXME: can we get a more interesting test case here?
+ self.assertEquals(self.app.description, "")
+
+ def testSetDescription(self):
+ "Node.description should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "description", "hello world")
+
+ # 'parent' (read-only Node instance):
+ def testGetParent(self):
+ # the app has a parent if gnome-shell is used, so parent.parent is a
+ # safe choice
+ if filter(lambda x: x.name == 'gnome-shell', self.app.applications()):
+ self.assertEquals(self.app.parent.parent, None)
+ self.assertEquals(self.app.children[0].parent, self.app)
+
+ def testSetParent(self):
+ "Node.parent should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "parent", None)
+
+ # 'children' (read-only list of Node instances):
+ def testGetChildren(self):
+ "A fresh gtk-demo app should have a single child: the window."
+ kids = self.app.children
+ self.assertEquals(len(kids), 1)
+ self.assertEquals(kids[0].name, "GTK+ Code Demos")
+ self.assertEquals(kids[0].roleName, "frame")
+
+ def testSetChildren(self):
+ "Node.children should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "children", [])
+
+ # 'text' (string):
+ @nottest
+ def testSimpleTextEntry(self):
+ """
+ Use gtk-demo's text entry example to check that reading and writing
+ Node.text works as expected
+ """
+ self.runDemo('Dialog and Message Boxes')
+ wnd = self.app.window('Dialogs')
+ wnd.button('Interactive Dialog').click()
+ dlg = self.app.dialog('Interactive Dialog')
+ entry1 = dlg.child(label='Entry 1')
+ entry2 = dlg.child(label='Entry 2')
+
+ # Try reading the entries:
+ self.assertEquals(entry1.text, "")
+ self.assertEquals(entry2.text, "")
+
+ # Set them...
+ entry1.text = "hello"
+ entry2.text = "world"
+
+ # Ensure that they got set:
+ self.assertEquals(entry1.text, "hello")
+ self.assertEquals(entry2.text, "world")
+
+ # and try again, searching for them again, to ensure it actually
+ # affected the UI:
+ self.assertEquals(dlg.child(label='Entry 1').text, "hello")
+ self.assertEquals(dlg.child(label='Entry 2').text, "world")
+
+ # Ensure app.text is None
+ self.assertEquals(self.app.text, None)
+
+ # Ensure a label's text is read-only as expected:
+ # FIXME: this doesn't work; the label has no 'text'; it has a name. we wan't a readonly text entry
+ # label = dlg.child('Entry 1')
+ # self.assertRaises(dogtail.tree.ReadOnlyError, label.text.__setattr__, "text", "hello world")
+
+ # FIXME: should we assert that things are logged and delays are added?
+ # FIXME: should have a test case involving the complex GtkTextView
+ # widget
+
+ @nottest
+ def testCaretOffset(self):
+ "Make sure the caret offset works as expected"
+ self.runDemo('Dialog and Message Boxes')
+ wnd = self.app.window('Dialogs')
+ entry1 = wnd.child(label='Entry 1')
+ entry2 = wnd.child(label='Entry 2')
+
+ # Try reading the entries:
+ self.assertEquals(entry1.text, '')
+ self.assertEquals(entry2.text, '')
+
+ # Set them...
+ s1 = "I just need a sentence"
+ s2 = "And maybe a second one to be sure"
+ entry1.text = s1
+ entry2.text = s2
+
+ # Make sure the caret offset is zero
+ self.assertEquals(entry1.caretOffset, 0)
+ self.assertEquals(entry2.caretOffset, 0)
+
+ # Set the caret offset to something ridiculous
+ entry1.caretOffset = len(s1 * 3)
+ entry2.caretOffset = len(s2 * 3)
+
+ # Make sure the caret offset only goes as far as the end of the string
+ self.assertEquals(entry1.caretOffset, len(s1))
+ self.assertEquals(entry2.caretOffset, len(s2))
+
+ def splitByOffsets(node, string):
+ # Verify the equality of node.text and string, word by word.
+ # I realize this doesn't really test dogtail itself, but that could
+ # change in the future and I don't want to throw the code away.
+ textIface = node.queryText()
+ endOffset = -1 # We only set this now so the loop looks nicer
+ startOffset = 0
+ while startOffset != len(string):
+ (text, startOffset, endOffset) = textIface.getTextAtOffset(
+ startOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
+ self.assertEquals(startOffset,
+ string.find(text, startOffset, endOffset))
+ startOffset = endOffset
+
+ splitByOffsets(entry1, s1)
+ splitByOffsets(entry2, s2)
+
+ # 'combovalue' (read/write string):
+ @nottest
+ def testSetComboValue(self):
+ self.runDemo('Combo boxes')
+ wnd = self.app.window('Combo boxes')
+ combo1 = wnd.child('Some stock icons').child(roleName='combo box')
+ combo1.combovalue = 'Clear'
+ self.assertEquals(combo1.combovalue, 'Clear')
+
+ # 'stateSet' (read-only StateSet instance):
+ def testGetStateSet(self):
+ "Node.sensitive should be False for the gtk-demo app node"
+ self.assert_(not self.app.sensitive)
+
+ def testSetStateSet(self):
+ "Node.stateSet should be read-only"
+ # FIXME should be AttributeError?
+ self.assertRaises(
+ RuntimeError, self.app.__setattr__, "states", pyatspi.StateSet())
+
+ # 'relations' (read-only list of atspi.Relation instances):
+ def testGetRelations(self):
+ # FIXME once relations are used for something other than labels
+ pass
+
+ # 'labelee' (read-only list of Node instances):
+ @nottest
+ def testGetLabelee(self):
+ "Entry1/2's labelee should be a text widget"
+ self.runDemo('Dialog and Message Boxes')
+ wnd = self.app.window('Dialogs')
+ label = wnd.child(roleName='label')
+ self.assertEquals(label.labelee.roleName, 'text')
+
+ def testSetLabelee(self):
+ "Node.labelee should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "labellee", None)
+
+ # 'labeler' (read-only list of Node instances):
+ # def testGetLabeler(self):
+ # "The text areas in the 'Dialogs' window should have labelers."
+ # self.runDemo('Dialog and Message Boxes')
+ # wnd = self.app.window('Dialogs')
+ # text = wnd.child(roleName = 'text')
+ # self.assertEquals(text.labeler.name, 'Entry 2')
+
+ def testSetLabeller(self):
+ "Node.labeller should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "labeller", None)
+
+ # 'sensitive' (read-only boolean):
+ def testGetSensitive(self):
+ """
+ Node.sensitive should not be set for the gtk-demo app.
+ It should be set for the window within the app.
+ """
+ self.assert_(not self.app.sensitive)
+ self.assert_(self.app.children[0].sensitive)
+
+ def testSetSensitive(self):
+ "Node.sensitive should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "sensitive", True)
+
+ # 'showing' (read-only boolean):
+ def testGetShowing(self):
+ "Node.showing should not be set for the gtk-demo. It should be set for the window within the app"
+ self.assert_(not self.app.showing)
+ self.assert_(self.app.children[0].showing)
+
+ def testSetShowing(self):
+ "Node.showing should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "showing", True)
+
+ # 'actions' (read-only list of Action instances):
+ def testGetActions(self):
+ "Node.actions should be an empty list for the app node"
+ self.assertEquals(len(self.app.actions), 0)
+
+ def testSetActions(self):
+ "Node.actions should be read-only"
+ self.assertRaises(AttributeError, self.app.__setattr__, "actions", {})
+
+ # 'extents' (readonly tuple):
+ def testGetExtents(self):
+ "Node.extents should be a 4-tuple for a window, with non-zero size"
+ (x, y, w, h) = self.app.children[0].extents
+ self.assert_(w > 0)
+ self.assert_(h > 0)
+
+ def testSetExtents(self):
+ "Node.extents should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "extents", (0, 0, 640, 480))
+
+ # 'position' (readonly tuple):
+ def testGetPosition(self):
+ "Node.position should be a 2-tuple for a window"
+ (x, y) = self.app.children[0].position
+
+ def testSetPosition(self):
+ "Node.position should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "position", (0, 0))
+
+ # 'size' (readonly tuple):
+ def testGetSize(self):
+ "Node.size should be a 2-tuple for a window, with non-zero values"
+ (w, h) = self.app.children[0].size
+ self.assert_(w > 0)
+ self.assert_(h > 0)
+
+ def testSetSize(self):
+ "Node.size should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "size", (640, 480))
+
+ # 'toolkitName' (readonly string):
+ def testGetToolkit(self):
+ self.assertEquals(self.app.toolkitName, "gtk")
+
+ def testSetToolkit(self):
+ "Node.toolkit should be read-only"
+ self.assertRaises(
+ AttributeError, self.app.__setattr__, "toolkitName", "gtk")
+
+ # 'ID'
+ def testGetID(self):
+ "Node.id should be numeric"
+ self.assertEquals(type(self.app.id), type(42))
+
+ def testSetID(self):
+ "Node.id should be read-only"
+ self.assertRaises(AttributeError, setattr, self.app, "id", 42)
+
+
+class TestSelection(GtkDemoTest):
+
+ def testTabs(self):
+ """
+ Tabs in the gtk-demo should be selectable, and be queryable for
+ "isSelected", and the results should change as they are selected.
+ """
+ # Use the Info/Source tabs of gtk-demo:
+ info = self.app.child('Info')
+ source = self.app.child('Source')
+
+ # Check initial state:
+ self.assert_(info.isSelected)
+ self.assert_(not source.isSelected)
+
+ # Select other tab:
+ source.select()
+
+ # Check new state:
+ self.assert_(not info.isSelected, False)
+ self.assert_(source.isSelected)
+
+ # Deselect tab:
+ # source.deselect()
+
+ # Check state:
+ # self.assert_(info.isSelected)
+ #self.assert_(not source.isSelected)
+
+
+class TestValue(GtkDemoTest):
+
+ def testGetValue(self):
+ "The scrollbar starts out at position zero."
+ sb = self.app.child(roleName='scroll bar')
+ self.assertEquals(sb.value, 0)
+
+# def testSetValue(self):
+# "Ensure that we can set the value of the scrollbar."
+# sb = self.app.child(roleName = 'scroll bar')
+# sb.value = 100
+# self.assertEquals(sb.value, 100)
+
+ def testMinValue(self):
+ "Ensure that the minimum value for the scrollbar is correct."
+ sb = self.app.child(roleName='scroll bar')
+ self.assertEquals(sb.minValue, 0)
+
+# def testMaxValue(self):
+# "Ensure that the maximum value for the scrollbar is plausible."
+# sb = self.app.child(roleName = 'scroll bar')
+# self.assert_(sb.maxValue > 250)
+
+ def testMinValueIncrement(self):
+ "Ensure that the minimum value increment of the scrollbar is an int."
+ sb = self.app.child(roleName='scroll bar')
+ self.assertEquals(sb.minValueIncrement, sb.minValueIncrement)
+
+
+class TestSearching(GtkDemoTest):
+ # FIXME: should test the various predicates and the search methods of Node
+
+ def testFindChildren(self):
+ """
+ Ensure that there are the correct number of table cells in the list
+ of demos.
+ """
+ pred = dogtail.predicate.GenericPredicate(roleName='table cell')
+ tableCells = self.app.findChildren(pred)
+
+ def get_table_cells_recursively(node):
+ counter = 0
+ for child in node.children:
+ if child.roleName == 'table cell':
+ counter += 1
+ counter += get_table_cells_recursively(child)
+ return counter
+
+ counter = get_table_cells_recursively(self.app)
+ self.assertEquals(len(tableCells), counter)
+
+ def testFindChildren2(self):
+ "Ensure that there are two tabs in the second page tab list."
+ pred = dogtail.predicate.GenericPredicate(roleName='page tab list')
+ pageTabLists = self.app.findChildren(pred)
+ pred = dogtail.predicate.GenericPredicate(roleName='page tab')
+ # The second page tab list is the one with the 'Info' and 'Source' tabs
+ pageTabs = pageTabLists[1].findChildren(pred)
+ self.assertEquals(len(pageTabs), 6)
+
+ def testFindChildrenLambdas(self):
+ self.runDemo('Dialog and Message Boxes')
+ wnd = self.app.window('Dialogs')
+ texts = wnd.findChildren(lambda x: x.roleName=='text', isLambda = True)
+ self.assertEquals(len(texts), 2)
+ self.assertEquals(texts[0].roleName, 'text')
+ self.assertEquals(texts[1].roleName, 'text')
+ texts1 = wnd.findChildren(lambda x: x.roleName=='text' and x.labeler.name == 'Entry 1', isLambda = True)
+ self.assertEquals(len(texts1), 1)
+ self.assertEquals(texts1[0].roleName, 'text')
+ self.assertEquals(texts1[0].labeler.name, 'Entry 1')
+ texts2 = wnd.findChildren(lambda x: x.roleName=='text' and x.showing, isLambda = True)
+ self.assertEquals(len(texts2), 2)
+ self.assertEquals(texts2[0].roleName, 'text')
+ self.assertTrue(texts2[0].showing)
+ self.assertEquals(texts2[1].roleName, 'text')
+ self.assertTrue(texts2[1].showing)
+
+ # def testFindChildrenNonRecursive(self):
+ # """
+ # Ensure that there are the correct number of table cells in the Tree
+ # Store demo.
+ # """
+ # The next several lines exist to expand the 'Tree View' item and
+ # scroll down, so that runDemo() will work.
+ # FIXME: make runDemo() handle this for us.
+ # treeViewCell = self.app.child('Tree View', roleName = 'table cell')
+ # treeViewCell.typeText('+')
+ # dogtail.tree.doDelay()
+ # sb = self.app.child(roleName = 'scroll bar')
+ # sb.value = sb.maxValue
+ # self.runDemo('Tree Store')
+ # wnd = self.app.window('Card planning sheet')
+ # table = wnd.child(roleName = 'tree table')
+ # pred = dogtail.predicate.GenericPredicate(roleName = 'table cell')
+ # dogtail.config.config.childrenLimit = 10000
+ # cells = table.findChildren(pred, recursive = False)
+ # direct_cells = filter(lambda cell: cell.roleName=='table cell', table.children)
+ # self.assertEquals(len(cells), len(direct_cells))
+
+
+class TestActions(GtkDemoTest):
+ # FIXME: should test the various actions
+ pass
+
+
+class TestProcedural(GtkDemoTest):
+ # FIXME: should test the procedural API
+ pass
+
+
+class TestExceptions(GtkDemoTest):
+
+ @nottest
+ def test_exception(self):
+ # Kill the gtk-demo prematurely:
+ import os
+ import signal
+ os.kill(self.pid, signal.SIGKILL)
+
+ from gi.repository import GLib
+ # Ensure that we get an exception when we try to work further with it:
+ self.assertRaises(GLib.GError, self.app.dump)
+
+
+class TestConfiguration(unittest.TestCase):
+
+ def test_get_set_all_properties(self):
+ for option in dogtail.config.config.defaults.keys():
+ print("Setting config.%s property" % option)
+ value = ''
+ if 'Dir' in option:
+ value = '/tmp/dogtail/' # Special value for dir-related properties
+ dogtail.config.config.__setattr__(option, value)
+ self.assertEquals(dogtail.config.config.__getattr__(option), value)
+
+ def test_default_directories_created(self):
+ import os.path
+ self.assertEquals(
+ os.path.isdir(dogtail.config.config.scratchDir), True)
+ self.assertEquals(os.path.isdir(dogtail.config.config.logDir), True)
+ self.assertEquals(os.path.isdir(dogtail.config.config.dataDir), True)
+
+ def test_set(self):
+ self.assertRaises(
+ AttributeError, setattr, dogtail.config.config, 'nosuchoption', 42)
+
+ def test_get(self):
+ self.assertRaises(
+ AttributeError, getattr, dogtail.config.config, 'nosuchoption')
+
+ def helper_create_directory_and_set_option(self, path, property_name):
+ import os.path
+ if os.path.isdir(path):
+ import shutil
+ shutil.rmtree(path)
+ dogtail.config.config.__setattr__(property_name, path)
+ self.assertEquals(os.path.isdir(path), True)
+
+ def test_create_scratch_directory(self):
+ new_folder = "/tmp/dt"
+ self.helper_create_directory_and_set_option(new_folder, 'scratchDir')
+
+ def test_create_data_directory(self):
+ new_folder = "/tmp/dt_data"
+ self.helper_create_directory_and_set_option(new_folder, 'dataDir')
+
+ def test_create_log_directory(self):
+ new_folder = "/tmp/dt_log"
+ self.helper_create_directory_and_set_option(new_folder, 'logDir')
+
+ def test_load(self):
+ dogtail.config.config.load({'actionDelay': 2.0})
+ self.assertEquals(dogtail.config.config.actionDelay, 2.0)
+
+ def test_reset(self):
+ default_actionDelay = dogtail.config.config.defaults['actionDelay']
+ dogtail.config.config.actionDelay = 2.0
+ dogtail.config.config.reset()
+ self.assertEquals(
+ dogtail.config.config.actionDelay, default_actionDelay)
+
+
+class TestDump(GtkDemoTest):
+
+ @nottest
+ def test_dump_to_stdout(self):
+ child = self.app.child('Source')
+ output = trap_stdout(child.dump)
+ self.assertEquals(
+ output,
+ """[page tab | Source]
+ [scroll pane | ]
+ [text | ]
+ [scroll bar | ]
+ [action | activate | ]
+ [scroll bar | ]
+ [action | activate | ]""")
diff --git a/tests/test_predicate.py b/tests/test_predicate.py
new file mode 100644
index 00000000000..a1287d2c29a
--- /dev/null
+++ b/tests/test_predicate.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Unit tests for the dogtail.predicate package
+"""
+import unittest
+import dogtail.tree
+import dogtail.predicate
+
+
+class TestPredicate(unittest.TestCase):
+
+ class DummyNode:
+
+ def __init__(self, name='', roleName='', description=''):
+ self.name = name
+ self.roleName = roleName
+ self.description = description
+ self.labeller = None
+
+ def test_capitalization(self):
+ self.assertEquals(
+ dogtail.predicate.makeCamel("gnome-terminal"), "gnomeTerminal")
+ self.assertEquals(
+ dogtail.predicate.makeCamel("Evolution - Mail"), "evolutionMail")
+ self.assertEquals(
+ dogtail.predicate.makeCamel(
+ 'self.assertEquals(makeCamel("Evolution - Mail"), "evolutionMail")'),
+ "selfAssertequalsMakecamelEvolutionMailEvolutionmail")
+
+ def test_abstract_class(self):
+ predicate = dogtail.predicate.Predicate()
+ self.assertRaises(NotImplementedError, predicate.satisfiedByNode, None)
+ self.assertRaises(
+ NotImplementedError, predicate.makeScriptMethodCall, None)
+ self.assertRaises(
+ NotImplementedError, predicate.makeScriptVariableName)
+ self.assertRaises(
+ NotImplementedError, predicate.describeSearchResult, None)
+
+ def test_correct_equality(self):
+ predicate1 = dogtail.predicate.Predicate()
+ predicate2 = dogtail.predicate.Predicate()
+ self.assertEquals(predicate1, predicate2)
+
+ def test_incorrect_equality(self):
+ predicate = dogtail.predicate.Predicate()
+ self.assertNotEquals(predicate, self)
+
+ def test_predicates_application(self):
+ dummyApp = self.DummyNode('dummy', 'application')
+ appPredicate = dogtail.predicate.IsAnApplicationNamed(dummyApp.name)
+ self.assertTrue(appPredicate.satisfiedByNode(dummyApp))
+ self.assertEquals(
+ appPredicate.makeScriptMethodCall(True), u'application("dummy")')
+ self.assertEquals(appPredicate.makeScriptVariableName(), u'dummyApp')
+
+ def test_predicates_window(self):
+ dummyWin = self.DummyNode('dummy', 'frame')
+ self.assertTrue(
+ dogtail.predicate.IsAWindow().satisfiedByNode(dummyWin))
+ self.assertEquals(
+ dogtail.predicate.IsAWindow().describeSearchResult(), 'window')
+
+ def test_predicates_window_named(self):
+ dummyWin = self.DummyNode('dummy', 'frame')
+ frameNamedPredicate = dogtail.predicate.IsAWindowNamed(dummyWin.name)
+ self.assertTrue(frameNamedPredicate.satisfiedByNode(dummyWin))
+ self.assertEquals(
+ frameNamedPredicate.makeScriptMethodCall(False), u'window("dummy")')
+ self.assertEquals(
+ frameNamedPredicate.makeScriptVariableName(), u'dummyWin')
+
+ def test_predicates_menu_named(self):
+ dummyMenu = self.DummyNode('dummy', 'menu')
+ menuNamedPredicate = dogtail.predicate.IsAMenuNamed(dummyMenu.name)
+ self.assertTrue(menuNamedPredicate.satisfiedByNode(dummyMenu))
+ self.assertEquals(menuNamedPredicate.makeScriptMethodCall(
+ False), u'menu("dummy", recursive=False)')
+ self.assertEquals(
+ menuNamedPredicate.makeScriptVariableName(), u'dummyMenu')
+
+ def test_predicates_menu_item_named(self):
+ dummyMenuItem = self.DummyNode('dummy', 'menu item')
+ menuItemNamedPredicate = dogtail.predicate.IsAMenuItemNamed(
+ dummyMenuItem.name)
+ self.assertTrue(menuItemNamedPredicate.satisfiedByNode(dummyMenuItem))
+ self.assertEquals(menuItemNamedPredicate.makeScriptMethodCall(
+ False), u'menuItem("dummy", recursive=False)')
+ self.assertEquals(
+ menuItemNamedPredicate.makeScriptVariableName(), u'dummyMenuItem')
+
+ def test_predicates_text_entry_named(self):
+ dummyText = self.DummyNode('dummy', 'text')
+ textNamedPredicate = dogtail.predicate.IsATextEntryNamed(
+ dummyText.name)
+ self.assertTrue(textNamedPredicate.satisfiedByNode(dummyText))
+ self.assertEquals(textNamedPredicate.makeScriptMethodCall(
+ False), u'textentry("dummy", recursive=False)')
+ self.assertEquals(
+ textNamedPredicate.makeScriptVariableName(), u'dummyEntry')
+
+ def test_predicates_button_named(self):
+ dummyButton = self.DummyNode('dummy', 'push button')
+ buttonNamedPredicate = dogtail.predicate.IsAButtonNamed(
+ dummyButton.name)
+ self.assertTrue(buttonNamedPredicate.satisfiedByNode(dummyButton))
+ self.assertEquals(buttonNamedPredicate.makeScriptMethodCall(
+ False), u'button("dummy", recursive=False)')
+ self.assertEquals(
+ buttonNamedPredicate.makeScriptVariableName(), u'dummyButton')
+
+ def test_predicates_page_tab_named(self):
+ dummyTab = self.DummyNode('dummy', 'page tab')
+ pageTabNamedPredicate = dogtail.predicate.IsATabNamed(dummyTab.name)
+ self.assertTrue(pageTabNamedPredicate.satisfiedByNode(dummyTab))
+ self.assertEquals(pageTabNamedPredicate.makeScriptMethodCall(
+ False), u'tab("dummy", recursive=False)')
+ self.assertEquals(
+ pageTabNamedPredicate.makeScriptVariableName(), u'dummyTab')
+
+ def test_predicates_generic_by_name(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ genericPredicateByName = dogtail.predicate.GenericPredicate(
+ name=dn1.name)
+ self.assertTrue(genericPredicateByName.satisfiedByNode(dn1))
+ self.assertEquals(genericPredicateByName.makeScriptMethodCall(
+ False), u'child( name="dummy name 1", recursive=False)')
+ self.assertEquals(
+ genericPredicateByName.makeScriptVariableName(), u'dummyName1Node')
+
+ def test_predicates_generic_by_roleName(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ genericPredicateByRole = dogtail.predicate.GenericPredicate(
+ roleName=dn1.roleName)
+ self.assertTrue(genericPredicateByRole.satisfiedByNode(dn1))
+ self.assertEquals(genericPredicateByRole.makeScriptMethodCall(
+ False), u"child( roleName='dummy role 1', recursive=False)")
+ self.assertEquals(
+ genericPredicateByRole.makeScriptVariableName(), u'dummyRole1Node')
+
+ def test_predicates_generic_by_description(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ genericPredicateByDescription = dogtail.predicate.GenericPredicate(
+ description=dn1.description)
+ self.assertTrue(genericPredicateByDescription.satisfiedByNode(dn1))
+ self.assertEquals(genericPredicateByDescription.makeScriptMethodCall(
+ False), u"child( description='dummy desc 1', recursive=False)")
+ self.assertEquals(
+ genericPredicateByDescription.makeScriptVariableName(), u'dummyDesc1Node')
+
+ def test_predicates_generic_by_label(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ dn2 = self.DummyNode('dummy name 2', 'dummy role 2', 'dummy desc 2')
+ dn2.labeller = dn1
+ genericPredicateByLabel = dogtail.predicate.GenericPredicate(
+ label=dn1.name)
+ self.assertTrue(genericPredicateByLabel.satisfiedByNode(dn2))
+ self.assertEquals(genericPredicateByLabel.makeScriptMethodCall(
+ False), u'child(label="dummy name 1", recursive=False)')
+ self.assertEquals(
+ genericPredicateByLabel.makeScriptVariableName(), u'dummyName1Node')
+
+ def test_predicates_named(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ genericNamedPredicate = dogtail.predicate.IsNamed(dn1.name)
+ self.assertTrue(genericNamedPredicate.satisfiedByNode(dn1))
+ self.assertEquals(genericNamedPredicate.makeScriptMethodCall(
+ False), u'child(name="dummy name 1", recursive=False)')
+ self.assertEquals(
+ genericNamedPredicate.makeScriptVariableName(), u'dummyName1Node')
+
+ def test_predicates_labelled_as(self):
+ dn1 = self.DummyNode('dummy name 1', 'dummy role 1', 'dummy desc 1')
+ dn2 = self.DummyNode('dummy name 2', 'dummy role 2', 'dummy desc 2')
+ dn2.labeller = dn1
+ genericLabelledPredicate = dogtail.predicate.IsLabelledAs(dn1.name)
+ self.assertTrue(genericLabelledPredicate.satisfiedByNode(dn2))
+ self.assertFalse(genericLabelledPredicate.satisfiedByNode(dn1))
+ self.assertEquals(genericLabelledPredicate.makeScriptMethodCall(
+ False), u'child(label="dummy name 1", recursive=False)')
+ self.assertEquals(
+ genericLabelledPredicate.makeScriptVariableName(), u'dummyName1Node')
+
+ def test_predicates_dialog_named(self):
+ dn1 = self.DummyNode('dummy name 1', 'dialog', 'dummy desc 1')
+ genericNamedPredicate = dogtail.predicate.IsADialogNamed(dn1.name)
+ self.assertTrue(genericNamedPredicate.satisfiedByNode(dn1))
+ self.assertEquals(genericNamedPredicate.makeScriptMethodCall(
+ False), u'dialog("dummy name 1")')
+ self.assertEquals(
+ genericNamedPredicate.makeScriptVariableName(), u'dummyName1Dlg')
diff --git a/tests/test_procedural.py b/tests/test_procedural.py
new file mode 100644
index 00000000000..820678a37b1
--- /dev/null
+++ b/tests/test_procedural.py
@@ -0,0 +1,213 @@
+#!/usr/bin/python
+"""
+Unit tests for the dogtail.procedural API
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+from dogtail.procedural import focus, keyCombo, deselect, select, click, tree, FocusError, run, config, type
+config.logDebugToFile = False
+config.logDebugToStdOut = True
+import pyatspi
+from gtkdemotest import GtkDemoTest, trap_stdout
+from nose.tools import nottest
+
+
+class GtkDemoTest(GtkDemoTest):
+
+ def setUp(self):
+ self.pid = run('gtk3-demo')
+ self.app = focus.application.node
+
+ # FIXME: Implement doubleclick() in d.procedural and override the other
+ # methods of Node.GtkDemoTest
+
+
+class TestFocusApplication(GtkDemoTest):
+
+ def testFocusingBogusNameWithoutAFatalError(self):
+ config.fatalErrors = False
+ output = trap_stdout(focus.application, "should not be found")
+ self.assertTrue(
+ 'The requested widget could not be focused: "should not be found" application' in output)
+
+ def testThrowExceptionOnFocusingBogusName(self):
+ config.fatalErrors = True
+ self.assertRaises(FocusError, focus.application, "should not be found")
+
+ def testFocusingBasic(self):
+ "Ensure that focus.application() sets focus.application.node properly"
+ focus.application.node = None
+ focus.application("gtk3-demo")
+ self.assertEquals(focus.application.node, self.app)
+
+
+class TestFocusWindow(GtkDemoTest):
+
+ def testFocusingBogusNameWithoutAFatalError(self):
+ config.fatalErrors = False
+ output = trap_stdout(focus.window, "should not be found")
+ self.assertEquals(focus.window.node, None)
+ self.assertTrue(
+ 'The requested widget could not be focused: "should not be found" window' in output)
+
+ def testThrowExceptionOnFocusingBogusName(self):
+ config.fatalErrors = True
+ self.assertRaises(FocusError, focus.window, "should not be found")
+
+
+class TestFocusDialog(GtkDemoTest):
+
+ def testFocusingBogusNameWithoutAFatalError(self):
+ config.fatalErrors = False
+ output = trap_stdout(focus.dialog, "should not be found")
+ self.assertEquals(focus.dialog.node, None)
+ self.assertTrue(
+ 'The requested widget could not be focused: "should not be found" dialog' in output)
+
+ def testThrowExceptionOnFocusingBogusName(self):
+ config.fatalErrors = True
+ self.assertRaises(FocusError, focus.dialog, "should not be found")
+
+
+class TestFocusWidget(GtkDemoTest):
+
+ def testFocusingEmptyName(self):
+ self.assertRaises(TypeError, focus.widget)
+
+ def testFocusingBogusNameWithoutAFatalError(self):
+ config.fatalErrors = False
+ output = trap_stdout(focus.widget, "should not be found")
+ self.assertEquals(focus.widget.node, None)
+ self.assertTrue(
+ 'The requested widget could not be focused: child with name="should not be found"' in output)
+
+ def testThrowExceptionOnFocusingBogusName(self):
+ config.fatalErrors = True
+ self.assertRaises(FocusError, focus.widget, "should not be found")
+
+ def testFocusingBasic(self):
+ "Ensure that focus.widget('foo') finds a node with name 'foo'"
+ focus.widget("Application window")
+ self.assertEquals(focus.widget.name, "Application window")
+
+
+class TestFocus(GtkDemoTest):
+
+ def testInitialState(self):
+ "Ensure that focus.widget, focus.dialog and focus.window are None " + \
+ "initially."
+ self.assertEquals(focus.widget.node, None)
+ self.assertEquals(focus.dialog.node, None)
+ self.assertEquals(focus.window.node, None)
+
+ def testFocusingApp(self):
+ "Ensure that focus.app() works"
+ focus.app.node = None
+ focus.app('gtk3-demo')
+ self.assertEquals(focus.app.node, self.app)
+
+ def testFocusingAppViaApplication(self):
+ "Ensure that focus.application() works"
+ focus.app.node = None
+ focus.application('gtk3-demo')
+ self.assertEquals(focus.app.node, self.app)
+
+ def testFocusGettingBogusAttribute(self):
+ self.assertRaises(AttributeError, getattr, focus, 'nosuchtype')
+
+ def testFocusSettingBogusAttribute(self):
+ self.assertRaises(
+ AttributeError, setattr, focus, 'nosuchtype', 'nothing')
+
+ def testFocusingRoleName(self):
+ "Ensure that focus.widget(roleName=...) works."
+ focus.widget(roleName='page tab')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_PAGE_TAB)
+
+ def testFocusMenu(self):
+ self.runDemo('Application window')
+ focus.window('Application Window')
+ focus.menu('File')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_MENU)
+
+ def testFocusMenuItem(self):
+ self.runDemo('Application window')
+ focus.window('Application Window')
+ click.menu('File')
+ focus.menuItem('New')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_MENU_ITEM)
+
+ def testFocusButton(self):
+ self.runDemo('Application window')
+ focus.window('Application Window')
+ focus.button('Open')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_PUSH_BUTTON)
+
+ def testFocusTable(self):
+ self.runDemo('Builder')
+ focus.window('GtkBuilder demo')
+ focus.table('')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_TABLE)
+
+ def testFocusTableCell(self):
+ self.runDemo('Builder')
+ focus.window('GtkBuilder demo')
+ focus.tableCell('')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_TABLE_CELL)
+
+ def testFocusText(self):
+ self.runDemo('Application window')
+ focus.window('Application Window')
+ focus.text('')
+ self.assert_(isinstance(focus.widget.node, tree.Node))
+ self.assertEquals(focus.widget.node.role, pyatspi.ROLE_TEXT)
+
+
+class TestKeyCombo(GtkDemoTest):
+
+ def testKeyCombo(self):
+ self.runDemo('Application window')
+ focus.window('Application Window')
+ keyCombo("<ctrl>a")
+ focus.dialog('About GTK+ Code Demos')
+
+
+class TestActions(GtkDemoTest):
+
+ def testClick(self):
+ click('Source')
+ self.assertTrue(focus.widget.isSelected)
+
+ def testClickWithRaw(self):
+ click('Source', raw=True)
+ self.assertTrue(focus.widget.isSelected)
+
+ def testSelect(self):
+ select('Source')
+ self.assertTrue(focus.widget.isSelected)
+
+ @nottest
+ def testDeselect(self):
+ type('Icon View')
+ click('Icon View')
+ type('+')
+ self.runDemo('Icon View Basics')
+ focus.window('GtkIconView demo')
+
+ focus.widget(roleName='icon')
+ select()
+ deselect()
+ self.assertFalse(focus.widget.isSelected)
+
+ def testTyping(self):
+ self.runDemo('Dialog and Message Boxes')
+ focus.window('Dialogs')
+ focus.widget(roleName='text')
+ type("hello world")
+ self.assertEquals(focus.widget.node.text, 'hello world')
diff --git a/tests/test_utils.py b/tests/test_utils.py
new file mode 100644
index 00000000000..02f781bb34c
--- /dev/null
+++ b/tests/test_utils.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+"""
+Unit tests for the dogtail.procedural API
+"""
+__author__ = "Zack Cerza <zcerza@redhat.com>"
+
+import unittest
+import dogtail.tree
+import dogtail.predicate
+dogtail.config.config.logDebugToFile = False
+dogtail.config.config.logDebugToStdOut = True
+from gtkdemotest import GtkDemoTest
+
+
+class TestScreenshot(GtkDemoTest):
+
+ def make_expected_and_compare(self, actual_path, jpg_tolerance=None):
+ extension = actual_path.split('.')[-1]
+ expected_path = actual_path.replace(extension, "expected." + extension)
+
+ import os
+ os.system("gnome-screenshot -f %s" % expected_path)
+
+ command = ["compare", "-metric", "MAE",
+ actual_path, expected_path, "output"]
+ import subprocess
+ p = subprocess.Popen(command, stderr=subprocess.PIPE)
+ output, error = p.communicate()
+
+ import re
+ m = re.search(r"\((.*)\)", error)
+ self.assertTrue(0.1 >= float(m.group(1)))
+
+ def test_screenshot_incorrect_timestamp(self):
+ self.assertRaises(
+ TypeError, dogtail.utils.screenshot, "timeStamp", None)
+
+ def test_screenshot_default(self):
+ actual_path = dogtail.utils.screenshot()
+ self.make_expected_and_compare(actual_path)
+
+ def test_screenshot_basename(self):
+ actual_path = dogtail.utils.screenshot("basename")
+ self.make_expected_and_compare(actual_path)
+
+ def test_screenshot_no_time_stamp(self):
+ actual_path = dogtail.utils.screenshot(timeStamp=False)
+ self.make_expected_and_compare(actual_path)
+
+ def test_screenshot_jpeg(self):
+ actual_path = dogtail.utils.screenshot("basename.jpg")
+ self.make_expected_and_compare(actual_path, jpg_tolerance=True)
+
+ def test_screenshot_unknown_format(self):
+ self.assertRaises(ValueError, dogtail.utils.screenshot, "basename.dat")
+
+
+class TestA11Y(unittest.TestCase):
+
+ def test_bail_when_a11y_disabled(self):
+ self.assertRaises(SystemExit, dogtail.utils.bailBecauseA11yIsDisabled)
+
+ def test_enable_a11y(self):
+ dogtail.utils.enableA11y()