~ubuntu-branches/debian/experimental/dogtail/experimental

« back to all changes in this revision

Viewing changes to dogtail/tree.py

  • Committer: Package Import Robot
  • Author(s): Alessio Treglia
  • Date: 2013-02-09 16:01:44 UTC
  • mfrom: (1.2.2)
  • Revision ID: package-import@ubuntu.com-20130209160144-k2yk35tll4eew9wg
Tags: 0.8.1-1
* New maintainer. (Closes: #696136) (Closes: #553898)
* Set packaging format to 3.0 (quilt).
* New upstream release (Closes: #486452):
  - String exceptions are not used anymore. (Closes: #585287)
  - Fix missing check in findChildren(), tree.py (Closes: #485758)
* ACK NMUs:
  - Convert APT's API patch into the quilt format. (Closes: #572087)
  - Convert Ludovico Gardenghi's patch into the quilt
    format. (Closes: #485752)
* Fix desktop file as Freedesktop.org's per-spec.
* Migrate from CDBS + python-support to DH short-form + dh_python2.
* Move to section python.
* Refresh {,Build-}Depends lists.
* Remove xbase-clients from Depends. (Closes: #601486)
* Add Homepage field. (Closes: #572570)
* Add watch file.
* Add gbp config file.
* Refresh debian/copyright to meet copyright format 1.0.
* Install NEWS as upstream changelog.
* Bump Standards.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
"""Makes some sense of the AT-SPI API
2
2
 
3
3
The tree API handles various things for you:
4
 
- fixes most timing issues
5
 
- can automatically generate (hopefully) highly-readable logs of what the
 
4
    - fixes most timing issues
 
5
    - can automatically generate (hopefully) highly-readable logs of what the
6
6
script is doing
7
 
- traps various UI malfunctions, raising exceptions for them (again,
 
7
    - traps various UI malfunctions, raising exceptions for them (again,
8
8
hopefully improving the logs)
9
9
 
10
10
The most important class is Node. Each Node is an element of the desktop UI.
14
14
elements (root, the applications, the windows, and the widgets) are represented
15
15
as instances of Node in a tree (provided that the program of interest is
16
16
correctly exporting its user-interface to the accessibility system). The Node
17
 
class is a wrapper around Accessible and the various Accessible interfaces.
 
17
class is a mixin for Accessible and the various Accessible interfaces.
18
18
 
19
19
The Action class represents an action that the accessibility layer exports as
20
20
performable on a specific node, such as clicking on it. It's a wrapper around
21
 
AccessibleAction.
 
21
Accessibility.Action.
22
22
 
23
23
We often want to look for a node, based on some criteria, and this is provided
24
24
by the Predicate class.
25
25
 
26
26
Dogtail implements a high-level searching system, for finding a node (or
27
 
nodes) satisfying whatever criteria you are interested in.      It does this with
 
27
nodes) satisfying whatever criteria you are interested in. It does this with
28
28
a 'backoff and retry' algorithm. This fixes most timing problems e.g. when a
29
29
dialog is in the process of opening but hasn't yet done so.
30
30
 
36
36
all of this process in the debug log by setting 'config.debugSearching' to True
37
37
 
38
38
We also automatically add a short delay after each action
39
 
('config.defaultDelay' gives the time in seconds).      We'd hoped that the search
 
39
('config.defaultDelay' gives the time in seconds). We'd hoped that the search
40
40
backoff and retry code would eliminate the need for this, but unfortunately we
41
 
still run into timing issues.    For example, Evolution (and probably most
 
41
still run into timing issues. For example, Evolution (and probably most
42
42
other apps) set things up on new dialogs and wizard pages as they appear, and
43
43
we can run into 'setting wars' where the app resets the widgetry to defaults
44
44
after our script has already filled out the desired values, and so we lose our
60
60
David Malcolm <dmalcolm@redhat.com>
61
61
"""
62
62
 
63
 
from utils import checkForA11y
64
 
checkForA11y()
 
63
from config import config
 
64
if config.checkForA11y:
 
65
    from utils import checkForA11y
 
66
    checkForA11y()
65
67
 
 
68
import sys
66
69
import re
67
70
import predicate
68
71
from datetime import datetime
69
72
from time import sleep
70
 
from config import config
71
73
from utils import doDelay
72
74
from utils import Blinker
 
75
from utils import Lock
73
76
import rawinput
74
77
import path
75
78
 
76
79
from logging import debugLogger as logger
77
80
 
78
81
try:
79
 
    import atspi
 
82
    import pyatspi
 
83
    import Accessibility
80
84
except ImportError:
81
 
    # If atspi can't be imported, fail.
82
85
    raise ImportError, "Error importing the AT-SPI bindings"
83
86
 
84
 
# We optionally import the bindings for libwnck.
 
87
# We optionally import the bindings for libWnck.
85
88
try:
86
 
    import wnck
 
89
    from gi.repository import Wnck
87
90
    gotWnck = True
88
91
except ImportError:
89
92
    # Skip this warning, since the functionality is almost entirely nonworking anyway.
90
93
    #print "Warning: Dogtail could not import the Python bindings for libwnck. Window-manager manipulation will not be available."
91
94
    gotWnck = False
92
95
 
 
96
haveWarnedAboutChildrenLimit = False
 
97
 
93
98
class SearchError(Exception):
94
99
    pass
95
100
 
96
 
class ReadOnlyError(TypeError):
97
 
    """
98
 
    This attribute is not writeable.
99
 
    """
100
 
    message = "Cannot set %s. It is read-only."
101
 
    def __init__(self, attr):
102
 
        self.attr = attr
103
 
 
104
 
    def __str__(self):
105
 
        return self.message % self.attr
106
 
 
107
101
class NotSensitiveError(Exception):
108
102
    """
109
103
    The widget is not sensitive.
148
142
        self.__action = action
149
143
        self.__index = index
150
144
 
151
 
    def __getattr__ (self, attr):
152
 
        if attr == "name":
153
 
            return self.__action.getName (self.__index).lower()
154
 
        elif attr == "description":
155
 
            return self.__action.getDescription (self.__index)
156
 
        elif attr == "keyBinding":
157
 
            return self.__action.getKeyBinding (self.__index)
158
 
        else: raise AttributeError, attr
 
145
    @property
 
146
    def name(self): return self.__action.getName(self.__index)
 
147
 
 
148
    @property
 
149
    def description(self): return self.__action.getDescription(self.__index)
 
150
 
 
151
    @property
 
152
    def keyBinding(self): return self.__action.getKeyBinding(self.__index)
159
153
 
160
154
    def __str__ (self):
161
 
        """
162
 
        Plain-text representation of the Action.
163
 
        """
164
 
        string = "Action node='%s' name='%s' description='%s' keybinding='%s'" % (self.node, self.name, self.description, self.keyBinding)
165
 
        return string
 
155
        return "[action | %s | %s ]" % \
 
156
            (self.name, self.keyBinding)
166
157
 
167
158
    def do (self):
168
159
        """
169
160
        Performs the given tree.Action, with appropriate delays and logging.
170
161
        """
171
 
        assert isinstance(self, Action)
172
162
        logger.log("%s on %s"%(self.name, self.node.getLogString()))
173
163
        if not self.node.sensitive:
174
164
            if config.ensureSensitivity:
178
168
                logger.log("Warning: " + str(nSE))
179
169
        if config.blinkOnActions: self.node.blink()
180
170
        result = self.__action.doAction (self.__index)
181
 
        doDelay()
 
171
        doDelay(config.actionDelay)
182
172
        return result
183
173
 
 
174
 
184
175
class Node:
185
176
    """
186
 
    A node in the tree of UI elements. It wraps an Accessible and
187
 
    exposes its useful members. It also has a debugName which is set
188
 
    up automatically when doing searches.
189
 
 
190
 
    Node instances have various attributes synthesized, to make it easy to
191
 
    get and the underlying accessible data. Many more attributes can be
192
 
    added as desired.
193
 
 
194
 
    'name' (read-only string):
195
 
    Wraps Accessible_getName on the Node's underlying Accessible
196
 
 
197
 
    'roleName' (read-only string):
198
 
    Wraps Accessible_getRoleName on the Node's underlying Accessible
199
 
 
200
 
    'role' (read-only atspi role enum):
201
 
    Wraps Accessible_getRole on the Node's underlying Accessible
202
 
 
203
 
    'description' (read-only string):
204
 
    Wraps Accessible_getDescription on the Node's underlying Accessible
205
 
 
206
 
    'parent' (read-only Node instance):
207
 
    A Node instance wrapping the parent, or None. Wraps Accessible_getParent
208
 
 
209
 
    'children' (read-only list of Node instances):
210
 
    The children of this node, wrapping getChildCount and getChildAtIndex
211
 
 
212
 
    'text' (string):
213
 
    For instances wrapping AccessibleText, the text. This is read-only,
214
 
    unless the instance wraps an AccessibleEditableText. In this case, you
215
 
    can write values to the attribute. This will get logged in the debug
216
 
    log, and a delay will be added. After the delay, the content of the
217
 
    node will be checked to ensure that it has the expected value. If it
218
 
    does not, an exception will be raised. This does not work for password
219
 
    dialogs (since all we get back are * characters). In this case, set
220
 
    the passwordText attribute instead.
221
 
 
222
 
    'passwordText' (write-only string):
223
 
    See documentation of 'text' attribute above.
224
 
 
225
 
    'caretOffset' (read/write int):
226
 
    For instances wrapping AccessibleText, the location of the text caret,
227
 
    expressed as an offset in characters.
228
 
 
229
 
    'combovalue' (write-only string):
230
 
    For comboboxes. Write to this attribute to set the combobox to the
231
 
    given value, with appropriate delays and logging.
232
 
 
233
 
    'stateSet' (read-only StateSet instance):
234
 
    Wraps Accessible_getStateSet; a set of boolean state flags
235
 
 
236
 
    'relations' (read-only list of atspi.Relation instances):
237
 
    Wraps Accessible_getRelationSet
238
 
 
239
 
    'labellee' (read-only list of Node instances):
240
 
    The node(s) that this node is a label for. Generated from 'relations'.
241
 
 
242
 
    'labeller' (read-only list of Node instances):
243
 
    The node(s) that is/are a label for this node. Generated from
244
 
    'relations'.
245
 
 
246
 
    'checked' (read-only boolean):
247
 
    Is this node checked? Only valid for checkboxes. Generated from stateSet
248
 
    based on presence of atspi.SPI_STATE_CHECKED.
249
 
 
250
 
    'sensitive' (read-only boolean):
251
 
    Is this node sensitive (i.e. not greyed out). Generated from stateSet
252
 
    based on presence of atspi.SPI_STATE_SENSITIVE
253
 
    Not all applications/toolkits seem to properly set this up.
254
 
 
255
 
    'showing' (read-only boolean):
256
 
    Generated from stateSet based on presence of atspi.SPI_STATE_SHOWING
257
 
 
258
 
    'focusable' (read-only boolean):
259
 
    Generated from stateSet based on presence of atspi.SPI_STATE_FOCUSABLE
260
 
 
261
 
    'focused' (read-only boolean):
262
 
    Generated from stateSet based on presence of atspi.SPI_STATE_FOCUSED
263
 
 
264
 
    'actions' (read-only list of Action instances):
265
 
    Generated from Accessible_getAction and AccessibleAction_getNActions
266
 
 
267
 
    For each action that is supported by a node, a method is hooked up,
268
 
    this can include the following list:
269
 
    'click'
270
 
    'press'
271
 
    'release'
272
 
    'activate'
273
 
    'jump'
274
 
    'check'
275
 
    'dock'
276
 
    'undock'
277
 
    'open'
278
 
    'menu'
279
 
 
280
 
    'extents' (readonly tuple):
281
 
    For instances wrapping a Component, the (x,y,w,h) screen extents of the
282
 
    component.
283
 
 
284
 
    'position' (readonly tuple):
285
 
    For instances wrapping a Component, the (x,y) screen position of the
286
 
    component.
287
 
 
288
 
    'size' (readonly tuple):
289
 
    For instances wrapping a Component, the (w,h) screen size of the component.
290
 
 
291
 
    'grabFocus':
292
 
    For instances wrapping a Component, attempt to set the keyboard input focus
293
 
    to that Node.
294
 
 
295
 
    'toolkit' (readonly string):
296
 
    For instances wrapping an application, the name of the toolkit.
297
 
 
298
 
    'version'
299
 
    For instances wrapping an application.
300
 
 
301
 
    'ID'
302
 
    For instances wrapping an application.
303
 
 
304
 
    'pause' (function)
305
 
    'resume' (function)
306
 
    For instances wrapping an application; probably don't work
 
177
    A node in the tree of UI elements. This class is mixed in with
 
178
    Accessibility.Accessible to both make it easier to use and to add
 
179
    additional functionality. It also has a debugName which is set up
 
180
    automatically when doing searches.
307
181
    """
308
182
 
309
 
    #Valid types of AT-SPI objects we wrap.
310
 
    contained = ('__accessible', '__action', '__component', '__text', '__editableText', '__selection')
311
 
 
312
 
    def __init__ (self, initializer):
313
 
        self.__hideChildren = False
314
 
        self.debugName = None
315
 
        if isinstance(initializer, atspi.Accessible):
316
 
            self.__accessible = initializer
317
 
        elif isinstance(initializer, Node):
318
 
            self.__accessible = initializer.__accessible
319
 
            self.debugName = getattr(initializer, 'debugName', None)
320
 
        else:
321
 
            raise "Unknown Node initializer"
322
 
        assert self.__accessible
323
 
 
324
 
        # Swallow the Action object, if it exists
325
 
        self.__action = self.__accessible.getAction()
326
 
        if self.__action is not None:
327
 
            def doAction(name):
328
 
                """
329
 
                Performs the tree.Action with the given name, with appropriate delays and logging,
330
 
                or raises the ActionNotSupported exception if the node doesn't support that particular
331
 
                action.
332
 
                """
333
 
                actions = self.actions
334
 
                if actions.has_key(name):
335
 
                    return actions[name].do()
336
 
                else:
337
 
                    raise ActionNotSupported(name, self)
338
 
            self.doAction = doAction
339
 
 
340
 
        # Swallow the Component object, if it exists
341
 
        self.__component = self.__accessible.getComponent()
342
 
        if self.__component is not None:
343
 
            def grabFocus():
344
 
                self.__component.grabFocus()
345
 
                doDelay()
346
 
            self.grabFocus = grabFocus
347
 
 
348
 
            def rawClick(button = 1):
349
 
                """
350
 
                Generates a raw mouse click event whether or not the Node has a 'click' action, using the specified button.
351
 
                1 is left,
352
 
                2 is middle,
353
 
                3 is right.
354
 
                """
355
 
                extents = self.extents
356
 
                position = (extents[0], extents[1])
357
 
                size = (extents[2], extents[3])
358
 
                clickX = position[0] + 0.5 * size[0]
359
 
                clickY = position[1] + 0.5 * size[1]
360
 
                if config.debugSearching:
361
 
                    logger.log("raw click on %s %s at (%s,%s)"%(self.name, self.getLogString(), str(clickX), str(clickY)))
362
 
                rawinput.click(clickX, clickY, button)
363
 
            self.rawClick = rawClick
364
 
 
365
 
        # Swallow the Text object, if it exists
366
 
        self.__text = self.__accessible.getText()
367
 
        if self.__text is not None:
368
 
            self.addSelection = self.__text.addSelection
369
 
            self.getNSelections = self.__text.getNSelections
370
 
            self.removeSelection = self.__text.removeSelection
371
 
            self.setSelection = self.__text.setSelection
372
 
 
373
 
        # Swallow the EditableText object, if it exists
374
 
        self.__editableText = self.__accessible.getEditableText()
375
 
        if self.__editableText is not None:
376
 
            self.setAttributes = self.__editableText.setAttributes
377
 
 
378
 
        # Swallow the Hypertext object, if it exists
379
 
        self.__hypertext = self.__accessible.getHypertext()
380
 
 
381
 
        # Swallow the Selection object, if it exists
382
 
        self.__selection = self.__accessible.getSelection()
383
 
        if self.__selection is not None:
384
 
            def selectAll():
385
 
                """
386
 
                Selects all children.
387
 
                """
388
 
                return self.__selection.selectAll()
389
 
            self.selectAll = selectAll
390
 
 
391
 
            def deselectAll():
392
 
                """
393
 
                Deselects all selected children.
394
 
                """
395
 
                return self.__selection.clearSelection()
396
 
            self.deselectAll = deselectAll
397
 
 
398
 
        # Implement select() for children of nodes with Selection interfaces.
399
 
        parent = self.parent
400
 
        if parent and parent._Node__selection:
401
 
            def select():
402
 
                """
403
 
                Selects the node, relative to its siblings.
404
 
                """
405
 
                return self.parent._Node__selection.selectChild(self.indexInParent)
406
 
            self.select = select
407
 
 
408
 
            def deselect():
409
 
                """
410
 
                Deselects the node, relative to its siblings.
411
 
                """
412
 
                selectedIndex = 0
413
 
                parent = self.parent
414
 
                for i in range(self.indexInParent):
415
 
                    if parent.children[i].isSelected:
416
 
                        selectedIndex+=1
417
 
                return parent._Node__selection.deselectSelectedChild(selectedIndex)
418
 
            self.deselect = select
419
 
 
420
 
        # Add more objects here. Nobody uses them yet, so I haven't.
421
 
        # You also need to change the __getattr__ and __setattr__ functions.
422
 
 
423
 
    # It'd be nice to know if two objects are "identical". However, the
424
 
    # approach below does not work, since atspi.Accessible doesn't know
425
 
    # how to check if two cspi.Accessible objects are "identical" either. :(
426
 
    #def __cmp__ (self, node):
427
 
    #        return self.__accessible == node.__accessible
428
 
 
429
 
    def __getattr__ (self, attr):
430
 
        if False: pass
431
 
 
432
 
        # Attributes from Applications
433
 
        # (self.__accessible will be an Application and not an Accessible)
434
 
        elif attr == "toolkit" and type(self.__accessible) == atspi.Application:
435
 
            return self.__accessible.getToolkit()
436
 
        elif attr == "version" and type(self.__accessible) == atspi.Application:
437
 
            return self.__accessible.getVersion()
438
 
        elif attr == "ID" and type(self.__accessible) == atspi.Application:
439
 
            return self.__accessible.getID()
440
 
        # These two appear to be useless, so they're lazily bound. :)
441
 
        elif attr == "pause" and type(self.__accessible) == atspi.Application:
442
 
            return self.__accessible.pause
443
 
        elif attr == "resume" and type(self.__accessible) == atspi.Application:
444
 
            return self.__accessible.resume
445
 
 
446
 
        # Attributes from the Accessible object
447
 
        elif attr == "name":
448
 
            return self.__accessible.getName()
449
 
        elif attr == "role":
450
 
            return self.__accessible.getRole()
451
 
        elif attr == "roleName":
452
 
            return self.__accessible.getRoleName()
453
 
        elif attr == "description":
454
 
            return self.__accessible.getDescription()
455
 
        elif attr == "parent":
456
 
            parentAcc = self.__accessible.getParent ()
457
 
            if parentAcc:
458
 
                parent = Node (parentAcc)
459
 
                return parent
460
 
        elif attr =="indexInParent":
461
 
            return self.__accessible.getIndexInParent()
462
 
        elif attr == "children":
463
 
            if self.__hideChildren: return
464
 
            children = []
465
 
            for i in xrange (self.__accessible.getChildCount ()):
466
 
                if isinstance(self, Root):
467
 
                    try: a = self.__accessible.getChildAtIndex (i)
468
 
                    except atspi.SpiException:
469
 
                        import traceback
470
 
                        logger.log(traceback.format_exc())
471
 
                else: a = self.__accessible.getChildAtIndex (i)
472
 
                # Workaround to GNOME bug #321273
473
 
                # http://bugzilla.gnome.org/show_bug.cgi?id=321273
474
 
                if a is not None: children.append (Node (a))
475
 
            # Attributes from the Hypertext object
476
 
            if self.__hypertext:
477
 
                for i in range(self.__hypertext.getNLinks()):
478
 
                    children.append(Link(self, self.__hypertext.getLink(i), i))
479
 
            return children
480
 
        elif attr == "stateSet":
481
 
            return self.__accessible.getStateSet()
482
 
        elif attr == "relations":
483
 
            return self.__accessible.getRelationSet()
484
 
        elif attr == "labellee":
485
 
            # Find the nodes that this node is a label for:
486
 
            # print self.relations
487
 
            for relation in self.relations:
488
 
                if relation.getRelationType() == atspi.SPI_RELATION_LABEL_FOR:
489
 
                    targets = relation.getTargets ()
490
 
                    return apply(Node, targets)
491
 
        elif attr == "labeller":
492
 
            # Find the nodes that are a label for this node:
493
 
            # print self.relations
494
 
            for relation in self.relations:
495
 
                if relation.getRelationType() == atspi.SPI_RELATION_LABELED_BY:
496
 
                    targets = relation.getTargets ()
497
 
                    return apply(Node, targets)
498
 
 
499
 
        # Attributes synthesized from the Accessible's stateSet:
500
 
        elif attr == "sensitive":
501
 
            return self.__accessible.getStateSet().contains(atspi.SPI_STATE_SENSITIVE)
502
 
        elif attr == "showing":
503
 
            return self.__accessible.getStateSet().contains(atspi.SPI_STATE_SHOWING)
504
 
        elif attr == "focusable":
505
 
            return self.__accessible.getStateSet().contains(atspi.SPI_STATE_FOCUSABLE)
506
 
        elif attr == "focused":
507
 
            return self.__accessible.getStateSet().contains(atspi.SPI_STATE_FOCUSED)
508
 
        elif attr == "checked":
509
 
            return self.__accessible.getStateSet().contains(atspi.SPI_STATE_CHECKED)
510
 
 
511
 
        # Attributes from the Action object
512
 
        elif attr == "actions":
513
 
            actions = {}
514
 
            if self.__action:
515
 
                for i in xrange (self.__action.getNActions ()):
516
 
                    action = (Action (self, self.__action, i))
517
 
                    actions[action.name] = action
 
183
    def __setupUserData(self):
 
184
        try: len(self.user_data)
 
185
        except (AttributeError, TypeError): self.user_data = {}
 
186
 
 
187
    @apply
 
188
    def debugName():
 
189
        doc = "debug name assigned during search operations"
 
190
        def fget(self):
 
191
            self.__setupUserData()
 
192
            return self.user_data.get('debugName', None)
 
193
 
 
194
        def fset(self, debugName):
 
195
            self.__setupUserData()
 
196
            self.user_data['debugName'] = debugName
 
197
 
 
198
        return property(**locals())
 
199
 
 
200
    ##
 
201
    # Accessible
 
202
    ##
 
203
    @property
 
204
    def dead(self):
 
205
        """Is the node dead (defunct) ?"""
 
206
        try:
 
207
            if self.roleName == 'invalid': return True
 
208
            n = self.role
 
209
            n = self.name
 
210
            if len(self) > 0: n = self[0]
 
211
        except: return True
 
212
        return False
 
213
 
 
214
    @property
 
215
    def children(self):
 
216
        """a list of this Accessible's children"""
 
217
        if self.parent and self.parent.roleName == 'hyper link':
 
218
            print self.parent.role
 
219
            return []
 
220
        children = []
 
221
        childCount = self.childCount
 
222
        if childCount > config.childrenLimit:
 
223
            global haveWarnedAboutChildrenLimit
 
224
            if not haveWarnedAboutChildrenLimit:
 
225
                logger.log("Only returning %s children. You may change "
 
226
                    "config.childrenLimit if you wish. This message will only"
 
227
                    " be printed once." % str(config.childrenLimit))
 
228
                haveWarnedAboutChildrenLimit = True
 
229
                childCount = config.childrenLimit
 
230
        for i in range(childCount):
 
231
            # Workaround for GNOME bug #465103
 
232
            # also solution for GNOME bug #321273
 
233
            try:
 
234
                child = self[i]
 
235
            except LookupError: child = None
 
236
            if child: children.append(child)
 
237
 
 
238
        invalidChildren = childCount - len(children)
 
239
        if invalidChildren and config.debugSearching:
 
240
            logger.log("Skipped %s invalid children of %s" % \
 
241
                    (invalidChildren, str(self)))
 
242
        try:
 
243
            ht = self.queryHypertext()
 
244
            for li in range(ht.getNLinks()):
 
245
                link = ht.getLink(li)
 
246
                for ai in range(link.nAnchors):
 
247
                    child = link.getObject(ai)
 
248
                    child.__setupUserData()
 
249
                    child.user_data['linkAnchor'] = \
 
250
                            LinkAnchor(node = child, \
 
251
                                        hypertext = ht, \
 
252
                                        linkIndex = li, \
 
253
                                        anchorIndex = ai )
 
254
                    children.append(child)
 
255
        except NotImplementedError: pass
 
256
 
 
257
        return children
 
258
 
 
259
    roleName = property(Accessibility.Accessible.getRoleName)
 
260
 
 
261
    role = property(Accessibility.Accessible.getRole)
 
262
 
 
263
    indexInParent = property(Accessibility.Accessible.getIndexInParent)
 
264
 
 
265
    ##
 
266
    # Action
 
267
    ##
 
268
 
 
269
    ## Needed to be renamed from doAction due to conflicts
 
270
    ## with 'Accessibility.Accessible.doAction' in gtk3 branch
 
271
    def doActionNamed(self, name):
 
272
        """
 
273
        Perform the action with the specified name. For a list of actions
 
274
        supported by this instance, check the 'actions' property.
 
275
        """
 
276
        actions = self.actions
 
277
        if name in actions:
 
278
            return actions[name].do()
 
279
        raise ActionNotSupported(name, self)
 
280
 
 
281
    @property
 
282
    def actions(self):
 
283
        """
 
284
        A dictionary of supported action names as keys, with Action objects as
 
285
        values. Common action names include:
 
286
 
 
287
        'click' 'press' 'release' 'activate' 'jump' 'check' 'dock' 'undock'
 
288
        'open' 'menu'
 
289
        """
 
290
        actions = {}
 
291
        try:
 
292
            action = self.queryAction()
 
293
            for i in range(action.nActions):
 
294
                a = Action(self, action, i)
 
295
                actions[action.getName(i)] = a
 
296
        finally:
518
297
            return actions
519
298
 
520
 
        # Attributes from the Component object
521
 
        elif attr == "extents":
522
 
            if self.__component:
523
 
                return self.__component.getExtents()
524
 
        elif attr == "position":
525
 
            if self.__component:
526
 
                return self.__component.getPosition()
527
 
        elif attr == "size":
528
 
            if self.__component:
529
 
                # This always returns [0, 0]
530
 
                #return self.__component.getSize()
531
 
                extents = self.__component.getExtents()
532
 
                size = (extents[2], extents[3])
533
 
                return size
534
 
 
535
 
        # Attributes from the Text object
536
 
        elif attr == "text":
537
 
            if self.__text:
538
 
                return self.__text.getText(0, 32767)
539
 
        elif attr == "caretOffset":
540
 
            if self.__text:
541
 
                return self.__text.getCaretOffset()
542
 
 
543
 
        # Attributes from the Selection object
544
 
        elif attr == "isSelected":
545
 
            parent = self.parent
546
 
            if parent and parent._Node__selection:
547
 
                return self.parent._Node__selection.isChildSelected(self.indexInParent)
548
 
        elif attr == "selectedChildren":
549
 
            if self.__hideChildren:
550
 
                return
551
 
            selectedChildren = []
552
 
            parent = self.parent
553
 
            if parent and self.parent._Node__selection:
554
 
                for i in xrange(self.__selection.getNSelectedChildren()):
555
 
                    selectedChildren.append(Node(self.__selection.getSelectedChild(i)))
556
 
            return selectedChildren
557
 
 
558
 
        else: raise AttributeError, attr
559
 
 
560
 
    def __setattr__ (self, attr, value):
561
 
        if False: pass
562
 
 
563
 
        # Are we swallowing an AT-SPI object?
564
 
        elif attr.replace('_Node', '') in self.contained:
565
 
            self.__dict__[attr] = value
566
 
 
567
 
        # Read-only attributes from Applications
568
 
        # (self.__accessible will be an Application and not an Accessible)
569
 
        elif attr in ["toolkit", "version", "ID"]:
570
 
            raise ReadOnlyError, attr
571
 
 
572
 
        # Read-only attributes from the Accessible object
573
 
        elif attr in ["name", "role", "roleName", "description", "parent",
574
 
                      "indexInParent", "children", "stateSet", "relations",
575
 
                      "labellee", "labeller"]:
576
 
            raise ReadOnlyError, attr
577
 
 
578
 
        # Read-only attributes synthesized from the Accessible's stateSet:
579
 
        elif attr in ["sensitive", "showing", "focusable", "focused", "checked"]:
580
 
            raise ReadOnlyError, attr
581
 
 
582
 
        # Read-only attributes from the Action object
583
 
        elif attr == "actions":
584
 
            raise ReadOnlyError, attr
585
 
 
586
 
        # Attributes from the Component object
587
 
        elif attr in ["extents", "position", "size"]:
588
 
            raise ReadOnlyError, attr
589
 
 
590
 
        # Attributes from the Text object
591
 
        elif attr=="caretOffset":
592
 
            if not self.__text:
593
 
                raise ReadOnlyError, attr
594
 
            self.__text.setCaretOffset(value)
595
 
 
596
 
        # Attributes from the EditableText object
597
 
        elif attr=="text":
598
 
            """
599
 
            Set the text of the node to the given value, with
600
 
            appropriate delays and logging, then test the result:
601
 
            """
602
 
            if not self.__editableText:
603
 
                raise ReadOnlyError, attr
604
 
            if config.debugSearching: logger.log("Setting text of %s to '%s'"%(self.getLogString(), value))
605
 
            self.__editableText.setTextContents (value)
606
 
            doDelay()
607
 
 
608
 
            #resultText = self.text
609
 
            #if resultText != value:
610
 
            #       raise "%s failed to have its text set to '%s'; value is '%s'"%(self.getLogString(), value, resultText)
611
 
 
612
 
        elif attr=='passwordText':
613
 
            """
614
 
            Set the text of the node to the given value, with
615
 
            appropriate delays and logging. We can't test the
616
 
            result, we'd only get * characters back.
617
 
            """
618
 
            if not self.__editableText:
619
 
                raise ReadOnlyError, attr
620
 
            logger.log("Setting text %s to password '%s'"%(self.getLogString(), value))
621
 
            self.__editableText.setTextContents (value)
622
 
            doDelay()
623
 
 
624
 
        elif attr=="combovalue":
625
 
            """
626
 
            Set the combobox to the given value, with appropriate delays and
627
 
            logging.
628
 
            """
629
 
            logger.log("Setting combobox %s to '%s'"%(self.getLogString(), value))
630
 
            self.childNamed(childName=value).click()
631
 
            doDelay()
632
 
        else:
633
 
            # FIXME: should we doing stuff like in the clause above???
634
 
            self.__dict__[attr] = value
 
299
    @apply
 
300
    def combovalue():
 
301
        doc = "The value (as a string) currently selected in the combo box."
 
302
 
 
303
        def fget(self): return self.name
 
304
 
 
305
        def fset(self, value):
 
306
            logger.log("Setting combobox %s to '%s'"%(self.getLogString(),
 
307
                value))
 
308
            self.childNamed(childName=value).doActionNamed('click')
 
309
            doDelay()
 
310
 
 
311
        return property(**locals())
 
312
 
 
313
    ##
 
314
    # Hypertext and Hyperlink
 
315
    ##
 
316
 
 
317
    @property
 
318
    def URI(self):
 
319
        try: return self.user_data['linkAnchor'].URI
 
320
        except (KeyError, AttributeError): raise NotImplementedError
 
321
 
 
322
 
 
323
    ##
 
324
    # Text and EditableText
 
325
    ##
 
326
 
 
327
    @apply
 
328
    def text():
 
329
        doc = """For instances with an AccessibleText interface, the text as a
 
330
    string. This is read-only, unless the instance also has an
 
331
    AccessibleEditableText interface. In this case, you can write values
 
332
    to the attribute. This will get logged in the debug log, and a delay
 
333
    will be added.
 
334
 
 
335
    If this instance corresponds to a password entry, use the passwordText
 
336
    property instead."""
 
337
 
 
338
        def fget(self):
 
339
            try: return self.queryText().getText(0,-1)
 
340
            except NotImplementedError: return None
 
341
        def fset(self, text):
 
342
             try:
 
343
                 if config.debugSearching:
 
344
                     msg = "Setting text of %s to %s"
 
345
                     # Let's not get too crazy if 'text' is really large...
 
346
                     # FIXME: Sometimes the next line screws up Unicode strings.
 
347
                     if len(text) > 140: txt = text[:134] + " [...]"
 
348
                     else: txt = text
 
349
                     logger.log(msg % (self.getLogString(), "'%s'" % txt))
 
350
                 self.queryEditableText().setTextContents(text)
 
351
             except NotImplementedError:
 
352
                 raise AttributeError, "can't set attribute"
 
353
 
 
354
        return property(**locals())
 
355
 
 
356
    @apply
 
357
    def caretOffset():
 
358
 
 
359
        def fget(self):
 
360
            """For instances with an AccessibleText interface, the caret
 
361
            offset as an integer."""
 
362
            return self.queryText().caretOffset
 
363
 
 
364
        def fset(self, offset):
 
365
            return self.queryText().setCaretOffset(offset)
 
366
 
 
367
        return property(**locals())
 
368
 
 
369
    ##
 
370
    # Component
 
371
    ##
 
372
 
 
373
    @property
 
374
    def position(self):
 
375
        """A tuple containing the position of the Accessible: (x, y)"""
 
376
        return self.queryComponent().getPosition(pyatspi.DESKTOP_COORDS)
 
377
 
 
378
    @property
 
379
    def size(self):
 
380
        """A tuple containing the size of the Accessible: (w, h)"""
 
381
        return self.queryComponent().getSize()
 
382
 
 
383
    @property
 
384
    def extents(self):
 
385
        """A tuple containing the location and size of the Accessible:
 
386
        (x, y, w, h)"""
 
387
        try:
 
388
            ex = self.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
 
389
            return (ex.x, ex.y, ex.width, ex.height)
 
390
        except NotImplementedError: return None
 
391
 
 
392
    def contains(self, x, y):
 
393
        try: return self.queryComponent().contains(x, y, pyatspi.DESKTOP_COORDS)
 
394
        except NotImplementedError: return False
 
395
 
 
396
    def getChildAtPoint(self, x, y):
 
397
        node = self
 
398
        while True:
 
399
            try:
 
400
                child = node.queryComponent().getAccessibleAtPoint(x, y,
 
401
                        pyatspi.DESKTOP_COORDS)
 
402
                if child and child.contains(x, y): node = child
 
403
                else: break
 
404
            except NotImplementedError: break
 
405
        if node and node.contains(x, y): return node
 
406
        else: return None
 
407
 
 
408
    def grabFocus(self):
 
409
        "Attempts to set the keyboard focus to this Accessible."
 
410
        return self.queryComponent().grabFocus()
 
411
 
 
412
    #def blink(self, count=2):
 
413
        #"""
 
414
        #Blink, baby!
 
415
        #"""
 
416
        #if not self.extents: return False
 
417
        #else:
 
418
            #(x, y, w, h) = self.extents
 
419
            #from utils import Blinker
 
420
            #blinkData = Blinker(x, y, w, h, count)
 
421
            #return True
 
422
 
 
423
    def click(self, button = 1):
 
424
        """
 
425
        Generates a raw mouse click event, using the specified button.
 
426
            - 1 is left,
 
427
            - 2 is middle,
 
428
            - 3 is right.
 
429
        """
 
430
        clickX = self.position[0] + self.size[0]/2
 
431
        clickY = self.position[1] + self.size[1]/2
 
432
        if config.debugSearching:
 
433
            logger.log("raw click on %s %s at (%s,%s)"%(self.name, self.getLogString(), str(clickX), str(clickY)))
 
434
        rawinput.click(clickX, clickY, button)
 
435
 
 
436
    def doubleClick(self, button = 1):
 
437
        """
 
438
        Generates a raw mouse double-click event, using the specified button.
 
439
        """
 
440
        clickX = self.position[0] + self.size[0]/2
 
441
        clickY = self.position[1] + self.size[1]/2
 
442
        if config.debugSearching:
 
443
            logger.log("raw click on %s %s at (%s,%s)"%(self.name, self.getLogString(), str(clickX), str(clickY)))
 
444
        rawinput.doubleClick(clickX, clickY, button)
 
445
 
 
446
 
 
447
    ##
 
448
    # RelationSet
 
449
    ##
 
450
    @property
 
451
    def labeler(self):
 
452
        """'labeller' (read-only list of Node instances):
 
453
        The node(s) that is/are a label for this node. Generated from
 
454
        'relations'.
 
455
        """
 
456
        relationSet = self.getRelationSet()
 
457
        for relation in relationSet:
 
458
            if relation.getRelationType() == pyatspi.RELATION_LABELLED_BY:
 
459
                    if relation.getNTargets() == 1:
 
460
                        return relation.getTarget(0)
 
461
                    targets = []
 
462
                    for i in range(relation.getNTargets()):
 
463
                        targets.append(relation.getTarget(i))
 
464
                    return targets
 
465
    labeller = labeler
 
466
 
 
467
    @property
 
468
    def labelee(self):
 
469
        """'labellee' (read-only list of Node instances):
 
470
        The node(s) that this node is a label for. Generated from 'relations'.
 
471
        """
 
472
        relationSet = self.getRelationSet()
 
473
        for relation in relationSet:
 
474
            if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
 
475
                    if relation.getNTargets() == 1:
 
476
                        return relation.getTarget(0)
 
477
                    targets = []
 
478
                    for i in range(relation.getNTargets()):
 
479
                        targets.append(relation.getTarget(i))
 
480
                    return targets
 
481
    labellee = labelee
 
482
 
 
483
    ##
 
484
    # StateSet
 
485
    ##
 
486
    @property
 
487
    def sensitive(self):
 
488
        """Is the Accessible sensitive (i.e. not greyed out)?"""
 
489
        return self.getState().contains(pyatspi.STATE_SENSITIVE)
 
490
 
 
491
    @property
 
492
    def showing(self):
 
493
        return self.getState().contains(pyatspi.STATE_SHOWING)
 
494
 
 
495
    @property
 
496
    def focusable(self):
 
497
        """Is the Accessible capable of having keyboard focus?"""
 
498
        return self.getState().contains(pyatspi.STATE_FOCUSABLE)
 
499
 
 
500
    @property
 
501
    def focused(self):
 
502
        """Does the Accessible have keyboard focus?"""
 
503
        return self.getState().contains(pyatspi.STATE_FOCUSED)
 
504
 
 
505
    @property
 
506
    def checked(self):
 
507
        """Is the Accessible a checked checkbox?"""
 
508
        return self.getState().contains(pyatspi.STATE_CHECKED)
 
509
 
 
510
    ##
 
511
    # Selection
 
512
    ##
 
513
 
 
514
    def selectAll(self):
 
515
        """Selects all children."""
 
516
        result = self.querySelection().selectAll()
 
517
        doDelay()
 
518
        return result
 
519
 
 
520
    def deselectAll(self):
 
521
        """Deselects all selected children."""
 
522
        result = self.querySelection().clearSelection()
 
523
        doDelay()
 
524
        return result
 
525
 
 
526
    def select(self):
 
527
        """Selects the Accessible."""
 
528
        try: parent = self.parent
 
529
        except AttributeError: raise NotImplementedError
 
530
        result = parent.querySelection().selectChild(self.indexInParent)
 
531
        doDelay()
 
532
        return result
 
533
 
 
534
    def deselect(self):
 
535
        """Deselects the Accessible."""
 
536
        try: parent = self.parent
 
537
        except AttributeError: raise NotImplementedError
 
538
        result = parent.querySelection().deselectChild(self.indexInParent)
 
539
        doDelay()
 
540
        return result
 
541
 
 
542
    @property
 
543
    def isSelected(self):
 
544
        """Is the Accessible selected?"""
 
545
        try: parent = self.parent
 
546
        except AttributeError: raise NotImplementedError
 
547
        return parent.querySelection().isChildSelected(self.indexInParent)
 
548
 
 
549
    @property
 
550
    def selectedChildren(self):
 
551
        """Returns a list of children that are selected."""
 
552
        #TODO: hideChildren for Hyperlinks?
 
553
        selection = self.querySelection()
 
554
        selectedChildren = []
 
555
        for i in xrange(selection.nSelectedChildren):
 
556
            selectedChildren.append(selection.getSelectedChild(i))
 
557
 
 
558
    ##
 
559
    # Value
 
560
    ##
 
561
 
 
562
    @apply
 
563
    def value():
 
564
        doc = "The value contained by the AccessibleValue interface."
 
565
        def fget(self):
 
566
            try: return self.queryValue().currentValue
 
567
            except NotImplementedError: pass
 
568
 
 
569
        def fset(self, value):
 
570
            self.queryValue().currentValue = value
 
571
 
 
572
        return property(**locals())
 
573
 
 
574
    @property
 
575
    def minValue(self):
 
576
        """The minimum value of self.value"""
 
577
        try: return self.queryValue().minimumValue
 
578
        except NotImplementedError: pass
 
579
 
 
580
    @property
 
581
    def minValueIncrement(self):
 
582
        """The minimum value increment of self.value"""
 
583
        try: return self.queryValue().minimumIncrement
 
584
        except NotImplementedError: pass
 
585
 
 
586
    @property
 
587
    def maxValue(self):
 
588
        """The maximum value of self.value"""
 
589
        try: return self.queryValue().maximumValue
 
590
        except NotImplementedError: pass
635
591
 
636
592
    def typeText(self, string):
637
593
        """
640
596
        """
641
597
        logger.log("Typing text into %s: '%s'"%(self.getLogString(), string))
642
598
 
643
 
        # Non-working implementation
644
 
        # Unfortunately, the call to AccessibleText_setCaretOffset fails for Evolution's gtkhtml composer for some reason
645
 
        if False:
646
 
            logger.log("caret offset: %s" % self.caretOffset)
647
 
            self.__editableText.insertText (self.caretOffset, text)
648
 
            self.caretOffset+=len(string) # FIXME: is this correct?
649
 
            logger.log("new caret offset: %s" % self.caretOffset)
650
 
 
651
599
        if self.focusable:
652
600
            if not self.focused:
653
601
                try: self.grabFocus()
654
 
                except: logger.log("Node is focusable but I can't grabFocus!")
 
602
                except Exception: logger.log("Node is focusable but I can't grabFocus!")
655
603
            rawinput.typeText(string)
656
604
        else:
657
 
            logger.log("Node is not focusable; falling back to setting text")
658
 
            node.text += string
 
605
            logger.log("Node is not focusable; falling back to inserting text")
 
606
            et = self.queryEditableText()
 
607
            et.insertText(self.caretOffset, string, len(string))
 
608
            self.caretOffset += len(string)
659
609
            doDelay()
660
610
 
661
611
    def keyCombo(self, comboString):
663
613
        if self.focusable:
664
614
            if not self.focused:
665
615
                try: self.grabFocus()
666
 
                except: logger.log("Node is focusable but I can't grabFocus!")
 
616
                except Exception: logger.log("Node is focusable but I can't grabFocus!")
667
617
        else: logger.log("Node is not focusable; trying key combo anyway")
668
618
        rawinput.keyCombo(comboString)
669
619
 
670
 
    def __str__ (self):
671
 
        """
672
 
        If debugName is set on this Node, returns debugName surrounded
673
 
        in curly braces.
674
 
        Otherwise, returns a plain-text representation of the most
675
 
        important attributes of the underlying Accessible.
676
 
        """
677
 
        if self.debugName:
678
 
            return "{" + self.debugName + "}"
679
 
        else:
680
 
            string = "Node"
681
 
            string = string + " roleName='%s'" % self.roleName
682
 
            string = string + " name='%s' description='%s'" % (self.name, self.description)
683
 
            if self.text is not None:
684
 
                string = string + " text='%s'" % self.text
685
 
            return string
686
620
 
687
621
    def getLogString(self):
688
622
        """
702
636
        assert isinstance(pred, predicate.Predicate)
703
637
        return pred.satisfiedByNode(self)
704
638
 
705
 
    def dump (self, type = 'plain'):
 
639
    def dump (self, type = 'plain', fileName = None):
706
640
        import dump
707
641
        dumper = getattr (dump, type)
708
 
        dumper (self)
 
642
        dumper (self, fileName)
709
643
 
710
644
    def getAbsoluteSearchPath(self):
711
645
        """
800
734
        else:
801
735
            return False
802
736
 
803
 
    # The canonical search method:
804
 
    def findChild(self, pred, recursive = True, debugName = None, retry = True, requireResult = True):
 
737
    def _fastFindChild(self, pred, recursive = True):
 
738
        """
 
739
        Searches for an Accessible using methods from pyatspi.utils
 
740
        """
 
741
        if isinstance(pred, predicate.Predicate): pred = pred.satisfiedByNode
 
742
        if not recursive:
 
743
            cIter = iter(self)
 
744
            while True:
 
745
                try: child = cIter.next()
 
746
                except StopIteration: break
 
747
                if child is not None:
 
748
                    if pred(child): return child
 
749
        else: return pyatspi.utils.findDescendant(self, pred)
 
750
 
 
751
    def findChild(self, pred, recursive = True, debugName = None, \
 
752
            retry = True, requireResult = True):
805
753
        """
806
754
        Search for a node satisyfing the predicate, returning a Node.
807
755
 
813
761
 
814
762
        If requireResult is True (the default), an exception is raised after all
815
763
        attempts have failed. If it is false, the function simply returns None.
816
 
 
817
 
        FIXME: make multiple attempts?
818
764
        """
819
 
 
820
 
        def findFirstChildSatisfying (parent, pred, recursive = True):
821
 
            """
822
 
            Internal helper function that does a one-shot search, recursing if need be.
823
 
            """
824
 
            # print "findFirstChildSatisfying(%s, %s, recursive=%s)"%(parent, pred,recursive)
825
 
            assert isinstance(pred, predicate.Predicate)
826
 
 
827
 
            try: children = parent.children
828
 
            except: return None
829
 
 
830
 
            for child in children:
831
 
                # print child
832
 
                if child.satisfies(pred):
833
 
                    return child
834
 
                else:
835
 
                    if recursive:
836
 
                        child = findFirstChildSatisfying(child, pred, recursive)
837
 
                        if child: return child
838
 
                # ...on to next child
839
 
 
840
765
        def describeSearch (parent, pred, recursive, debugName):
841
766
            """
842
767
            Internal helper function
843
768
            """
844
 
            if recursive:
845
 
                noun = "descendent"
846
 
            else:
847
 
                noun = "child"
848
 
 
849
 
            if debugName == None:
850
 
                debugName = pred.describeSearchResult()
851
 
 
 
769
            if recursive: noun = "descendent"
 
770
            else: noun = "child"
 
771
            if debugName == None: debugName = pred.describeSearchResult()
852
772
            return "%s of %s: %s"%(noun, parent.getLogString(), debugName)
853
773
 
854
774
        assert isinstance(pred, predicate.Predicate)
855
775
        numAttempts = 0
856
 
        while numAttempts<config.searchCutoffCount:
857
 
            if numAttempts>=config.searchWarningThreshold or config.debugSearching:
858
 
                logger.log("searching for %s (attempt %i)" % (describeSearch(self, pred, recursive, debugName), numAttempts))
 
776
        while numAttempts < config.searchCutoffCount:
 
777
            if numAttempts >= config.searchWarningThreshold or config.debugSearching:
 
778
                logger.log("searching for %s (attempt %i)" % \
 
779
                        (describeSearch(self, pred, recursive, debugName), numAttempts))
859
780
 
860
 
            result = findFirstChildSatisfying(self, pred, recursive)
 
781
            result = self._fastFindChild(pred.satisfiedByNode, recursive)
861
782
            if result:
862
783
                assert isinstance(result, Node)
863
 
                if debugName:
864
 
                    result.debugName = debugName
865
 
                else:
866
 
                    result.debugName = pred.describeSearchResult()
 
784
                if debugName: result.debugName = debugName
 
785
                else: result.debugName = pred.describeSearchResult()
867
786
                return result
868
787
            else:
869
788
                if not retry: break
870
 
                numAttempts+=1
 
789
                numAttempts += 1
871
790
                if config.debugSearching or config.debugSleep:
872
791
                    logger.log("sleeping for %f" % config.searchBackoffDuration)
873
792
                sleep(config.searchBackoffDuration)
879
798
        """
880
799
        Find all children/descendents satisfying the predicate.
881
800
        """
882
 
        assert isinstance(pred, predicate.Predicate)
883
 
 
884
 
        selfList = []
885
 
 
886
 
        try: children = self.children
887
 
        except: return None
888
 
 
889
 
        for child in children:
890
 
            if child.satisfies(pred): selfList.append(child)
891
 
            if recursive:
892
 
                childList = child.findChildren(pred, recursive)
893
 
                if childList:
894
 
                    for child in childList:
895
 
                        selfList.append(child)
896
 
            # ...on to next child
897
 
 
898
 
        if selfList: return selfList
 
801
        if isinstance(pred, predicate.Predicate): pred = pred.satisfiedByNode
 
802
        if not recursive:
 
803
            cIter = iter(self)
 
804
            result = []
 
805
            while True:
 
806
                try: child = cIter.next()
 
807
                except StopIteration: break
 
808
                if child is not None and pred(child): result.append(child)
 
809
            return result
 
810
        else: return pyatspi.utils.findAllDescendants(self, pred)
899
811
 
900
812
    # The canonical "search above this node" method:
901
813
    def findAncestor (self, pred):
915
827
 
916
828
 
917
829
    # Various wrapper/helper search methods:
918
 
    def child (self, name = '', roleName = '', description= '', label = '', recursive=True, debugName=None):
 
830
    def child (self, name = '', roleName = '', description= '', label = '', recursive=True, retry=True, debugName=None):
919
831
        """
920
832
        Finds a child satisying the given criteria.
921
833
 
923
835
        if no such child is found, and will eventually raise an exception. It
924
836
        also logs the search.
925
837
        """
926
 
        return self.findChild (predicate.GenericPredicate(name = name, roleName = roleName, description= description, label = label), recursive = recursive, debugName=debugName)
927
 
 
928
 
    # FIXME: does this clash with the "menu" action
 
838
        return self.findChild (predicate.GenericPredicate(name = name, roleName = roleName, description= description, label = label), recursive = recursive, retry = retry, debugName=debugName)
 
839
 
 
840
    def isChild(self, name = '', roleName = '', description= '', label = '', recursive=True, retry=False, debugName=None):
 
841
        """
 
842
        Determines whether a child satisying the given criteria exists.
 
843
 
 
844
        This is implemented using findChild, but will not automatically retry
 
845
        if no such child is found. To make the function retry multiple times set retry to True.
 
846
        Returns a boolean value depending on whether the child was eventually found. Similar to
 
847
        'child', yet it catches SearchError exception to provide for False results, will raise
 
848
        any other exceptions. It also logs the search.
 
849
        """
 
850
        found = True
 
851
        try:
 
852
            self.findChild (predicate.GenericPredicate(name = name, roleName = roleName, description= description, label = label), recursive = recursive, retry = retry, debugName=debugName)
 
853
        except SearchError:
 
854
            found = False
 
855
        return found
 
856
 
929
857
    def menu(self, menuName, recursive=True):
930
858
        """
931
859
        Search below this node for a menu with the given name.
974
902
        if no such child is found, and will eventually raise an exception. It
975
903
        also logs the search.
976
904
        """
977
 
        return self.findChild (predicate.IsLabelled(labelText), recursive)
 
905
        return self.findChild (predicate.IsLabelledAs(labelText), recursive)
978
906
 
979
907
    def childNamed(self, childName, recursive=True):
980
908
        """
1009
937
            result.append(self.description)
1010
938
        try:
1011
939
            children = self.children
1012
 
        except: return result
 
940
        except Exception: return result
1013
941
        for child in children:
1014
942
            result.extend(child.getUserVisibleStrings())
1015
943
        return result
1016
944
 
1017
 
    def blink(self, count = 2):
 
945
    def blink(self):
1018
946
        """
1019
947
        Blink, baby!
1020
948
        """
1022
950
            return False
1023
951
        else:
1024
952
            (x, y, w, h) = self.extents
1025
 
            blinkData = Blinker(x, y, w, h, count)
 
953
            blinkData = Blinker(x, y, w, h)
1026
954
            return True
1027
955
 
1028
956
 
1029
 
    def click(self):
1030
 
        """
1031
 
        If the Node supports an action called "click", do it, with appropriate delays and logging.
1032
 
        Otherwise, raise an ActionNotSupported exception.
1033
 
 
1034
 
        Note that this is just a shortcut to doAction('click'), as it is by far the most-used
1035
 
        action. To do any other action such as 'activate' or 'open', you must use doAction().
1036
 
        """
1037
 
        if self.__action is not None:
1038
 
            return self.doAction('click')
1039
 
        raise ActionNotSupported('click', self)
1040
 
 
1041
 
class Link(Node):
1042
 
    """
1043
 
    Class representing a hyperlink
1044
 
    """
1045
 
    contained = ('__hyperlink', '__node')
1046
 
 
1047
 
    def __init__(self, node, hyperlink, index):
1048
 
        self.debugName = None
1049
 
        self.parent = node
1050
 
        self.__hyperlink = hyperlink
1051
 
        self.__index = index
1052
 
        self.__node = Node(self.__hyperlink.getObject(self.__index))
1053
 
        # Somehow, if we allow the children to be seen, things get weird.
1054
 
        self.__node.__hideChildren = True
1055
 
 
1056
 
    def __getattr__(self, name):
1057
 
        if False: pass
1058
 
        elif name == 'URI':
1059
 
            # Note: This doesn't seem to work. It usually just causes python to hang.
1060
 
            return self.__hyperlink.getURI(self.__index)
1061
 
        else:
1062
 
            if name == 'children':
1063
 
                return
1064
 
            try:
1065
 
                result = getattr(self.__node, name)
1066
 
                return result
1067
 
            except AttributeError:
1068
 
                raise AttributeError, name
1069
 
 
1070
 
    def __setattr__(self, name, value):
1071
 
        self.__dict__[name] = value
 
957
class LinkAnchor:
 
958
    """
 
959
    Class storing info about an anchor within an Accessibility.Hyperlink, which
 
960
    is in turn stored within an Accessibility.Hypertext.
 
961
    """
 
962
 
 
963
    def __init__(self, node, hypertext, linkIndex, anchorIndex):
 
964
        self.node = node
 
965
        self.hypertext = hypertext
 
966
        self.linkIndex = linkIndex
 
967
        self.anchorIndex = anchorIndex
 
968
 
 
969
    @property
 
970
    def link(self):
 
971
        return self.hypertext.getLink(self.linkIndex)
 
972
 
 
973
    @property
 
974
    def URI(self):
 
975
        return self.link.getURI(self.anchorIndex)
 
976
 
1072
977
 
1073
978
class Root (Node):
1074
979
    """
1078
983
        """
1079
984
        Get all applications.
1080
985
        """
1081
 
        return root.findAllChildrenSatisfying(predicate.GenericPredicate(roleName="application"), recursive=False)
 
986
        return root.findChildren(predicate.GenericPredicate( \
 
987
                roleName="application"), recursive=False)
1082
988
 
1083
989
    def application(self, appName):
1084
990
        """
1089
995
        if no such child is found, and will eventually raise an exception. It
1090
996
        also logs the search.
1091
997
        """
1092
 
        return Application(root.findChild(predicate.IsAnApplicationNamed(appName),recursive=False))
 
998
        return root.findChild(predicate.IsAnApplicationNamed(appName),recursive=False)
1093
999
 
1094
1000
class Application (Node):
1095
1001
    def dialog(self, dialogName, recursive=False):
1118
1024
        The window will be automatically activated (raised and focused
1119
1025
        by the window manager) if wnck bindings are available.
1120
1026
        """
1121
 
        result = Window(self.findChild (predicate.IsAWindowNamed(windowName=windowName), recursive))
 
1027
        result = self.findChild (predicate.IsAWindowNamed(windowName=windowName), recursive)
1122
1028
        # FIXME: activate the WnckWindow ?
1123
1029
        #if gotWnck:
1124
1030
        #       result.activate()
1243
1149
 
1244
1150
        # FIXME: debug logging?
1245
1151
 
 
1152
Accessibility.Accessible.__bases__ = (Application,Root,Node,) + Accessibility.Accessible.__bases__
 
1153
 
1246
1154
try:
1247
 
    root = Root (atspi.registry.getDesktop ())
 
1155
    root = pyatspi.Registry.getDesktop(0)
1248
1156
    root.debugName= 'root'
1249
 
except AssertionError:
 
1157
except Exception:
1250
1158
    # Warn if AT-SPI's desktop object doesn't show up.
1251
1159
    logger.log("Error: AT-SPI's desktop is not visible. Do you have accessibility enabled?")
1252
1160
 
1256
1164
    logger.log("Warning: AT-SPI's desktop is visible but it has no children. Are you running any AT-SPI-aware applications?")
1257
1165
del children
1258
1166
 
 
1167
import os
 
1168
#sniff also imports from tree and we don't want to run this code from sniff itself
 
1169
if not os.path.exists('/tmp/sniff_running.lock'):
 
1170
    # tell sniff not to use auto-refresh while script using this module is running
 
1171
    sniff_lock = Lock(lockname='sniff_refresh.lock',randomize=False)
 
1172
    try:
 
1173
        sniff_lock.lock()
 
1174
    except OSError:
 
1175
        pass # lock was already present from other script instance or leftover from killed instance
 
1176
    # lock should unlock automatically on script exit.
 
1177
 
1259
1178
# Convenient place to set some debug variables:
1260
1179
#config.debugSearching = True
1261
1180
#config.absoluteNodePaths = True
 
1181
#config.logDebugToFile = False
 
1182