-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatspi.py
133 lines (97 loc) · 4.95 KB
/
atspi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from typing import List
import pyatspi
import threading
CLICKABLE_OBJECT_CLASSES = ['radio_button', 'check_box', 'push_button', 'combo_box', 'toggle_button', 'radio_menu_item']
ACTIONABLE_STATES = [pyatspi.STATE_VISIBLE, pyatspi.STATE_SHOWING]
ACTIONABLE_STATES_SET = set(pyatspi.StateSet(*ACTIONABLE_STATES).getStates())
ACTIVE_STATES = [pyatspi.STATE_ACTIVE]
ACTIVE_STATES_SET = set(ACTIVE_STATES)
SELECTABLE_STATES = [pyatspi.STATE_SELECTABLE]
SELECTABLE_STATES_SET = set(SELECTABLE_STATES)
class Registry(object):
def __init__(self, logger):
self.actionable_objects = []
self.listeners = []
self.logger = logger
self.registry = pyatspi.Registry()
def active_window(self):
desktop = self.registry.getDesktop(0)
for app in desktop:
app_state_set = app.getState().getStates()
if pyatspi.STATE_DEFUNCT in app_state_set:
self.logger.debug({'message': 'App defunct.', 'app': app})
continue
self.logger.debug({'message': 'Found app.', 'app': app, 'states': app_state_set})
if ACTIONABLE_STATES_SET.issubset(app_state_set):
self.logger.debug({'message': 'App visible and showing', 'app': app})
for window in app:
window_state_set = window.getState().getStates()
self.logger.debug({'message': 'Found app window.', 'window': window, 'states': window_state_set})
if ACTIVE_STATES_SET.issubset(window_state_set):
self.logger.debug({'message': 'App window is active.', 'app': app, 'window': window, 'role': window.getRoleName(), 'states': window.getState().getStates()})
return Window(self.logger, window)
class ActionableObject:
def __init__(self, object, component):
self.object = object
self.component = component
self.role = object.getRole()
self.position = component.getPosition(0)
self.size = component.getSize()
def do_action(self):
raise NotImplementedError()
# TODO(bkd): semantics that aren't awful
class ActionObject(ActionableObject):
def __init__(self, object, component, action, action_index):
ActionableObject.__init__(self, object, component)
self.action = action
self.action_index = action_index
@staticmethod
def create(object, component, action, click_ancestor=False) -> List[ActionableObject]:
action_objects = []
action_names = [action.getName(i) for i in range(0, action.nActions)]
for action_index, action_name in enumerate(action_names):
if action_name == 'click':
action_objects.append(ClickableObject(object, component, action, action_index))
elif action_name == 'press':
action_objects.append(PressableObject(object, component, action, action_index))
elif action_name == 'clickAncestor' and click_ancestor:
action_objects.append(SelectableObject(object, component, action, action_index))
return action_objects
def do_action(self):
self.action.doAction(self.action_index)
class ClickableObject(ActionObject):
pass
class PressableObject(ActionObject):
pass
class SelectableObject(ActionObject):
pass
# TODO(bkd): this didn't work, so I'm using clickAncestor
# def do_action(self):
# self.selection.selectChild(self.object.getIndexInParent())
class Window():
def __init__(self, logger, window):
self.logger = logger
self.logger.debug({'message': 'Introspecting window.', 'window': window})
self.window = window
self.actionable_objects = []
self.load_actionable_objects(0, window)
self.logger.debug({'message': 'Finished introspecting window.', 'window': window})
def filter_actionable_objects(self, actionable_object_types):
return list(filter(lambda x: type(x) in actionable_object_types, self.actionable_objects))
def load_actionable_objects(self, index, root):
root_states_set = set(root.getState().getStates())
self.logger.debug({'message': 'Introspecting actionable object.', 'window': self.window, 'root': root, 'role': root.getRole(), 'states': root_states_set})
component = root.queryComponent()
if ACTIONABLE_STATES_SET.issubset(root_states_set):
try:
action = root.queryAction()
# TODO(bkd): this feels like a hack
click_ancestor = [False, True][set([pyatspi.STATE_SELECTABLE]).issubset(root_states_set)]
self.actionable_objects.extend(ActionObject.create(root, component, action, click_ancestor))
except NotImplementedError:
pass
except Exception as error:
self.logger.error({'message': 'Error getting action.', 'error': error})
for child in root:
if child:
self.load_actionable_objects(index + 1, child)