aboutsummaryrefslogtreecommitdiffstats
path: root/build/lib.linux-x86_64-2.7/dogtail
diff options
context:
space:
mode:
Diffstat (limited to 'build/lib.linux-x86_64-2.7/dogtail')
-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
17 files changed, 4683 insertions, 0 deletions
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")