178
168
logger.log("Warning: " + str(nSE))
179
169
if config.blinkOnActions: self.node.blink()
180
170
result = self.__action.doAction (self.__index)
171
doDelay(config.actionDelay)
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.
190
Node instances have various attributes synthesized, to make it easy to
191
get and the underlying accessible data. Many more attributes can be
194
'name' (read-only string):
195
Wraps Accessible_getName on the Node's underlying Accessible
197
'roleName' (read-only string):
198
Wraps Accessible_getRoleName on the Node's underlying Accessible
200
'role' (read-only atspi role enum):
201
Wraps Accessible_getRole on the Node's underlying Accessible
203
'description' (read-only string):
204
Wraps Accessible_getDescription on the Node's underlying Accessible
206
'parent' (read-only Node instance):
207
A Node instance wrapping the parent, or None. Wraps Accessible_getParent
209
'children' (read-only list of Node instances):
210
The children of this node, wrapping getChildCount and getChildAtIndex
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.
222
'passwordText' (write-only string):
223
See documentation of 'text' attribute above.
225
'caretOffset' (read/write int):
226
For instances wrapping AccessibleText, the location of the text caret,
227
expressed as an offset in characters.
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.
233
'stateSet' (read-only StateSet instance):
234
Wraps Accessible_getStateSet; a set of boolean state flags
236
'relations' (read-only list of atspi.Relation instances):
237
Wraps Accessible_getRelationSet
239
'labellee' (read-only list of Node instances):
240
The node(s) that this node is a label for. Generated from 'relations'.
242
'labeller' (read-only list of Node instances):
243
The node(s) that is/are a label for this node. Generated from
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.
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.
255
'showing' (read-only boolean):
256
Generated from stateSet based on presence of atspi.SPI_STATE_SHOWING
258
'focusable' (read-only boolean):
259
Generated from stateSet based on presence of atspi.SPI_STATE_FOCUSABLE
261
'focused' (read-only boolean):
262
Generated from stateSet based on presence of atspi.SPI_STATE_FOCUSED
264
'actions' (read-only list of Action instances):
265
Generated from Accessible_getAction and AccessibleAction_getNActions
267
For each action that is supported by a node, a method is hooked up,
268
this can include the following list:
280
'extents' (readonly tuple):
281
For instances wrapping a Component, the (x,y,w,h) screen extents of the
284
'position' (readonly tuple):
285
For instances wrapping a Component, the (x,y) screen position of the
288
'size' (readonly tuple):
289
For instances wrapping a Component, the (w,h) screen size of the component.
292
For instances wrapping a Component, attempt to set the keyboard input focus
295
'toolkit' (readonly string):
296
For instances wrapping an application, the name of the toolkit.
299
For instances wrapping an application.
302
For instances wrapping an application.
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.
309
#Valid types of AT-SPI objects we wrap.
310
contained = ('__accessible', '__action', '__component', '__text', '__editableText', '__selection')
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)
321
raise "Unknown Node initializer"
322
assert self.__accessible
324
# Swallow the Action object, if it exists
325
self.__action = self.__accessible.getAction()
326
if self.__action is not None:
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
333
actions = self.actions
334
if actions.has_key(name):
335
return actions[name].do()
337
raise ActionNotSupported(name, self)
338
self.doAction = doAction
340
# Swallow the Component object, if it exists
341
self.__component = self.__accessible.getComponent()
342
if self.__component is not None:
344
self.__component.grabFocus()
346
self.grabFocus = grabFocus
348
def rawClick(button = 1):
350
Generates a raw mouse click event whether or not the Node has a 'click' action, using the specified button.
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
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
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
378
# Swallow the Hypertext object, if it exists
379
self.__hypertext = self.__accessible.getHypertext()
381
# Swallow the Selection object, if it exists
382
self.__selection = self.__accessible.getSelection()
383
if self.__selection is not None:
386
Selects all children.
388
return self.__selection.selectAll()
389
self.selectAll = selectAll
393
Deselects all selected children.
395
return self.__selection.clearSelection()
396
self.deselectAll = deselectAll
398
# Implement select() for children of nodes with Selection interfaces.
400
if parent and parent._Node__selection:
403
Selects the node, relative to its siblings.
405
return self.parent._Node__selection.selectChild(self.indexInParent)
410
Deselects the node, relative to its siblings.
414
for i in range(self.indexInParent):
415
if parent.children[i].isSelected:
417
return parent._Node__selection.deselectSelectedChild(selectedIndex)
418
self.deselect = select
420
# Add more objects here. Nobody uses them yet, so I haven't.
421
# You also need to change the __getattr__ and __setattr__ functions.
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
429
def __getattr__ (self, attr):
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
446
# Attributes from the Accessible object
448
return self.__accessible.getName()
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 ()
458
parent = Node (parentAcc)
460
elif attr =="indexInParent":
461
return self.__accessible.getIndexInParent()
462
elif attr == "children":
463
if self.__hideChildren: return
465
for i in xrange (self.__accessible.getChildCount ()):
466
if isinstance(self, Root):
467
try: a = self.__accessible.getChildAtIndex (i)
468
except atspi.SpiException:
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
477
for i in range(self.__hypertext.getNLinks()):
478
children.append(Link(self, self.__hypertext.getLink(i), i))
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)
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)
511
# Attributes from the Action object
512
elif attr == "actions":
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 = {}
189
doc = "debug name assigned during search operations"
191
self.__setupUserData()
192
return self.user_data.get('debugName', None)
194
def fset(self, debugName):
195
self.__setupUserData()
196
self.user_data['debugName'] = debugName
198
return property(**locals())
205
"""Is the node dead (defunct) ?"""
207
if self.roleName == 'invalid': return True
210
if len(self) > 0: n = self[0]
216
"""a list of this Accessible's children"""
217
if self.parent and self.parent.roleName == 'hyper link':
218
print self.parent.role
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
235
except LookupError: child = None
236
if child: children.append(child)
238
invalidChildren = childCount - len(children)
239
if invalidChildren and config.debugSearching:
240
logger.log("Skipped %s invalid children of %s" % \
241
(invalidChildren, str(self)))
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, \
254
children.append(child)
255
except NotImplementedError: pass
259
roleName = property(Accessibility.Accessible.getRoleName)
261
role = property(Accessibility.Accessible.getRole)
263
indexInParent = property(Accessibility.Accessible.getIndexInParent)
269
## Needed to be renamed from doAction due to conflicts
270
## with 'Accessibility.Accessible.doAction' in gtk3 branch
271
def doActionNamed(self, name):
273
Perform the action with the specified name. For a list of actions
274
supported by this instance, check the 'actions' property.
276
actions = self.actions
278
return actions[name].do()
279
raise ActionNotSupported(name, self)
284
A dictionary of supported action names as keys, with Action objects as
285
values. Common action names include:
287
'click' 'press' 'release' 'activate' 'jump' 'check' 'dock' 'undock'
292
action = self.queryAction()
293
for i in range(action.nActions):
294
a = Action(self, action, i)
295
actions[action.getName(i)] = a
520
# Attributes from the Component object
521
elif attr == "extents":
523
return self.__component.getExtents()
524
elif attr == "position":
526
return self.__component.getPosition()
529
# This always returns [0, 0]
530
#return self.__component.getSize()
531
extents = self.__component.getExtents()
532
size = (extents[2], extents[3])
535
# Attributes from the Text object
538
return self.__text.getText(0, 32767)
539
elif attr == "caretOffset":
541
return self.__text.getCaretOffset()
543
# Attributes from the Selection object
544
elif attr == "isSelected":
546
if parent and parent._Node__selection:
547
return self.parent._Node__selection.isChildSelected(self.indexInParent)
548
elif attr == "selectedChildren":
549
if self.__hideChildren:
551
selectedChildren = []
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
558
else: raise AttributeError, attr
560
def __setattr__ (self, attr, value):
563
# Are we swallowing an AT-SPI object?
564
elif attr.replace('_Node', '') in self.contained:
565
self.__dict__[attr] = value
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
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
578
# Read-only attributes synthesized from the Accessible's stateSet:
579
elif attr in ["sensitive", "showing", "focusable", "focused", "checked"]:
580
raise ReadOnlyError, attr
582
# Read-only attributes from the Action object
583
elif attr == "actions":
584
raise ReadOnlyError, attr
586
# Attributes from the Component object
587
elif attr in ["extents", "position", "size"]:
588
raise ReadOnlyError, attr
590
# Attributes from the Text object
591
elif attr=="caretOffset":
593
raise ReadOnlyError, attr
594
self.__text.setCaretOffset(value)
596
# Attributes from the EditableText object
599
Set the text of the node to the given value, with
600
appropriate delays and logging, then test the result:
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)
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)
612
elif attr=='passwordText':
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.
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)
624
elif attr=="combovalue":
626
Set the combobox to the given value, with appropriate delays and
629
logger.log("Setting combobox %s to '%s'"%(self.getLogString(), value))
630
self.childNamed(childName=value).click()
633
# FIXME: should we doing stuff like in the clause above???
634
self.__dict__[attr] = value
301
doc = "The value (as a string) currently selected in the combo box."
303
def fget(self): return self.name
305
def fset(self, value):
306
logger.log("Setting combobox %s to '%s'"%(self.getLogString(),
308
self.childNamed(childName=value).doActionNamed('click')
311
return property(**locals())
314
# Hypertext and Hyperlink
319
try: return self.user_data['linkAnchor'].URI
320
except (KeyError, AttributeError): raise NotImplementedError
324
# Text and EditableText
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
335
If this instance corresponds to a password entry, use the passwordText
339
try: return self.queryText().getText(0,-1)
340
except NotImplementedError: return None
341
def fset(self, text):
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] + " [...]"
349
logger.log(msg % (self.getLogString(), "'%s'" % txt))
350
self.queryEditableText().setTextContents(text)
351
except NotImplementedError:
352
raise AttributeError, "can't set attribute"
354
return property(**locals())
360
"""For instances with an AccessibleText interface, the caret
361
offset as an integer."""
362
return self.queryText().caretOffset
364
def fset(self, offset):
365
return self.queryText().setCaretOffset(offset)
367
return property(**locals())
375
"""A tuple containing the position of the Accessible: (x, y)"""
376
return self.queryComponent().getPosition(pyatspi.DESKTOP_COORDS)
380
"""A tuple containing the size of the Accessible: (w, h)"""
381
return self.queryComponent().getSize()
385
"""A tuple containing the location and size of the Accessible:
388
ex = self.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
389
return (ex.x, ex.y, ex.width, ex.height)
390
except NotImplementedError: return None
392
def contains(self, x, y):
393
try: return self.queryComponent().contains(x, y, pyatspi.DESKTOP_COORDS)
394
except NotImplementedError: return False
396
def getChildAtPoint(self, x, y):
400
child = node.queryComponent().getAccessibleAtPoint(x, y,
401
pyatspi.DESKTOP_COORDS)
402
if child and child.contains(x, y): node = child
404
except NotImplementedError: break
405
if node and node.contains(x, y): return node
409
"Attempts to set the keyboard focus to this Accessible."
410
return self.queryComponent().grabFocus()
412
#def blink(self, count=2):
416
#if not self.extents: return False
418
#(x, y, w, h) = self.extents
419
#from utils import Blinker
420
#blinkData = Blinker(x, y, w, h, count)
423
def click(self, button = 1):
425
Generates a raw mouse click event, using the specified button.
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)
436
def doubleClick(self, button = 1):
438
Generates a raw mouse double-click event, using the specified button.
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)
452
"""'labeller' (read-only list of Node instances):
453
The node(s) that is/are a label for this node. Generated from
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)
462
for i in range(relation.getNTargets()):
463
targets.append(relation.getTarget(i))
469
"""'labellee' (read-only list of Node instances):
470
The node(s) that this node is a label for. Generated from 'relations'.
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)
478
for i in range(relation.getNTargets()):
479
targets.append(relation.getTarget(i))
488
"""Is the Accessible sensitive (i.e. not greyed out)?"""
489
return self.getState().contains(pyatspi.STATE_SENSITIVE)
493
return self.getState().contains(pyatspi.STATE_SHOWING)
497
"""Is the Accessible capable of having keyboard focus?"""
498
return self.getState().contains(pyatspi.STATE_FOCUSABLE)
502
"""Does the Accessible have keyboard focus?"""
503
return self.getState().contains(pyatspi.STATE_FOCUSED)
507
"""Is the Accessible a checked checkbox?"""
508
return self.getState().contains(pyatspi.STATE_CHECKED)
515
"""Selects all children."""
516
result = self.querySelection().selectAll()
520
def deselectAll(self):
521
"""Deselects all selected children."""
522
result = self.querySelection().clearSelection()
527
"""Selects the Accessible."""
528
try: parent = self.parent
529
except AttributeError: raise NotImplementedError
530
result = parent.querySelection().selectChild(self.indexInParent)
535
"""Deselects the Accessible."""
536
try: parent = self.parent
537
except AttributeError: raise NotImplementedError
538
result = parent.querySelection().deselectChild(self.indexInParent)
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)
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))
564
doc = "The value contained by the AccessibleValue interface."
566
try: return self.queryValue().currentValue
567
except NotImplementedError: pass
569
def fset(self, value):
570
self.queryValue().currentValue = value
572
return property(**locals())
576
"""The minimum value of self.value"""
577
try: return self.queryValue().minimumValue
578
except NotImplementedError: pass
581
def minValueIncrement(self):
582
"""The minimum value increment of self.value"""
583
try: return self.queryValue().minimumIncrement
584
except NotImplementedError: pass
588
"""The maximum value of self.value"""
589
try: return self.queryValue().maximumValue
590
except NotImplementedError: pass
636
592
def typeText(self, string):