forked from nvaccess/nvda
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscriptHandler.py
292 lines (265 loc) · 10.6 KB
/
scriptHandler.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#scriptHandler.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2007-2017 NV Access Limited, Babbage B.V.
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import time
import weakref
import inspect
import config
import speech
import sayAllHandler
import appModuleHandler
import api
import queueHandler
from logHandler import log
import inputCore
import globalPluginHandler
import braille
import keyLabels
_numScriptsQueued=0 #Number of scripts that are queued to be executed
#: Number of scripts that send their gestures on that are queued to be executed or are currently being executed.
_numIncompleteInterceptedCommandScripts=0
_lastScriptTime=0 #Time in MS of when the last script was executed
_lastScriptRef=None #Holds a weakref to the last script that was executed
_lastScriptCount=0 #The amount of times the last script was repeated
_isScriptRunning=False
def _makeKbEmulateScript(scriptName):
import keyboardHandler
keyName = scriptName[3:]
emuGesture = keyboardHandler.KeyboardInputGesture.fromName(keyName)
func = lambda gesture: inputCore.manager.emulateGesture(emuGesture)
if isinstance(scriptName, unicode):
# __name__ must be str; i.e. can't be unicode.
scriptName = scriptName.encode("mbcs")
func.__name__ = "script_%s" % scriptName
func.__doc__ = _("Emulates pressing %s on the system keyboard") % emuGesture.displayName
return func
def _getObjScript(obj, gesture, globalMapScripts):
# Search the scripts from the global gesture maps.
for cls, scriptName in globalMapScripts:
if isinstance(obj, cls):
if scriptName is None:
# The global map specified that no script should execute for this gesture and object.
return None
if scriptName.startswith("kb:"):
# Emulate a key press.
return _makeKbEmulateScript(scriptName)
try:
return getattr(obj, "script_%s" % scriptName)
except AttributeError:
pass
# Search the object itself for in-built bindings.
return obj.getScript(gesture)
def findScript(gesture):
focus = api.getFocusObject()
if not focus:
return None
# Import late to avoid circular import.
# We need to import this here because this might be the first import of this module
# and it might be needed by global maps.
import globalCommands
globalMapScripts = []
globalMaps = [inputCore.manager.userGestureMap, inputCore.manager.localeGestureMap]
globalMap = braille.handler.display.gestureMap
if globalMap:
globalMaps.append(globalMap)
for globalMap in globalMaps:
for identifier in gesture.normalizedIdentifiers:
globalMapScripts.extend(globalMap.getScriptsForGesture(identifier))
# Gesture specific scriptable object.
obj = gesture.scriptableObject
if obj:
func = _getObjScript(obj, gesture, globalMapScripts)
if func:
return func
# Global plugin level.
for plugin in globalPluginHandler.runningPlugins:
func = _getObjScript(plugin, gesture, globalMapScripts)
if func:
return func
# App module level.
app = focus.appModule
if app:
func = _getObjScript(app, gesture, globalMapScripts)
if func:
return func
# Tree interceptor level.
treeInterceptor = focus.treeInterceptor
if treeInterceptor and treeInterceptor.isReady:
func = _getObjScript(treeInterceptor, gesture, globalMapScripts)
from browseMode import BrowseModeTreeInterceptor
if isinstance(treeInterceptor,BrowseModeTreeInterceptor):
func=treeInterceptor.getAlternativeScript(gesture,func)
if func and (not treeInterceptor.passThrough or getattr(func,"ignoreTreeInterceptorPassThrough",False)):
return func
# NVDAObject level.
func = _getObjScript(focus, gesture, globalMapScripts)
if func:
return func
for obj in reversed(api.getFocusAncestors()):
func = _getObjScript(obj, gesture, globalMapScripts)
if func and getattr(func, 'canPropagate', False):
return func
# Global commands.
func = _getObjScript(globalCommands.commands, gesture, globalMapScripts)
if func:
return func
return None
def getScriptName(script):
return script.__name__[7:]
def getScriptLocation(script):
try:
instance = script.__self__
except AttributeError:
# Not an instance method, so this must be a fake script.
return None
name=script.__name__
for cls in instance.__class__.__mro__:
if name in cls.__dict__:
return "%s.%s"%(cls.__module__,cls.__name__)
def _isInterceptedCommandScript(script):
return not getattr(script,'__doc__',None)
def _queueScriptCallback(script,gesture):
global _numScriptsQueued, _numIncompleteInterceptedCommandScripts
_numScriptsQueued-=1
executeScript(script,gesture)
if _isInterceptedCommandScript(script):
_numIncompleteInterceptedCommandScripts-=1
def queueScript(script,gesture):
global _numScriptsQueued, _numIncompleteInterceptedCommandScripts
_numScriptsQueued+=1
if _isInterceptedCommandScript(script):
_numIncompleteInterceptedCommandScripts+=1
queueHandler.queueFunction(queueHandler.eventQueue,_queueScriptCallback,script,gesture)
def willSayAllResume(gesture):
return config.conf['keyboard']['allowSkimReadingInSayAll']and gesture.wasInSayAll and getattr(gesture.script,'resumeSayAllMode',None)==sayAllHandler.lastSayAllMode
def executeScript(script,gesture):
"""Executes a given script (function) passing it the given gesture.
It also keeps track of the execution of duplicate scripts with in a certain amount of time, and counts how many times this happens.
Use L{getLastScriptRepeatCount} to find out this count value.
@param script: the function or method that should be executed. The function or method must take an argument of 'gesture'. This must be the same value as gesture.script, but its passed in here purely for performance.
@type script: callable.
@param gesture: the input gesture that activated this script
@type gesture: L{inputCore.InputGesture}
"""
global _lastScriptTime, _lastScriptCount, _lastScriptRef, _isScriptRunning
lastScriptRef=_lastScriptRef() if _lastScriptRef else None
#We don't allow the same script to be executed from with in itself, but we still should pass the key through
scriptFunc=getattr(script,"__func__",script)
if _isScriptRunning and lastScriptRef==scriptFunc:
return gesture.send()
_isScriptRunning=True
resumeSayAllMode=None
if willSayAllResume(gesture):
resumeSayAllMode=sayAllHandler.lastSayAllMode
try:
scriptTime=time.time()
scriptRef=weakref.ref(scriptFunc)
if (scriptTime-_lastScriptTime)<=0.5 and scriptFunc==lastScriptRef:
_lastScriptCount+=1
else:
_lastScriptCount=0
_lastScriptRef=scriptRef
_lastScriptTime=scriptTime
script(gesture)
except:
log.exception("error executing script: %s with gesture %r"%(script,gesture.displayName))
finally:
_isScriptRunning=False
if resumeSayAllMode is not None:
sayAllHandler.readText(resumeSayAllMode)
def getLastScriptRepeatCount():
"""The count of how many times the most recent script has been executed.
This should only be called from with in a script.
@returns: a value greater or equal to 0. If the script has not been repeated it is 0, if it has been repeated once its 1, and so forth.
@rtype: integer
"""
if (time.time()-_lastScriptTime)>0.5:
return 0
else:
return _lastScriptCount
def isScriptWaiting():
return bool(_numScriptsQueued)
def isCurrentScript(scriptFunc):
"""Finds out if the given script is equal to the script that L{isCurrentScript} is being called from.
@param scriptFunc: the script retreaved from ScriptableObject.getScript(gesture)
@type scriptFunc: Instance method
@returns: True if they are equal, False otherwise
@rtype: boolean
"""
try:
givenFunc=getattr(scriptFunc.im_self.__class__,scriptFunc.__name__)
except AttributeError:
log.debugWarning("Could not get unbound method from given script",exc_info=True)
return False
parentFrame=inspect.currentframe().f_back
try:
realObj=parentFrame.f_locals['self']
except KeyError:
log.debugWarning("Could not get self instance from parent frame instance method",exc_info=True)
return False
try:
realFunc=getattr(realObj.__class__,parentFrame.f_code.co_name)
except AttributeError:
log.debugWarning("Could not get unbound method from parent frame instance",exc_info=True)
return False
return givenFunc==realFunc
def script(
description="",
category=None,
gesture=None,
gestures=None,
canPropagate=False,
bypassInputHelp=False,
resumeSayAllMode=None
):
"""Define metadata for a script.
This function is to be used as a decorator to set metadata used by the scripting system and gesture editor.
It can only decorate methods which name start swith "script_"
@param description: A short translatable description of the script to be used in the gesture editor, etc.
@type description: string
@param category: The category of the script displayed in the gesture editor.
@type category: string
@param gesture: A gesture associated with this script.
@type gesture: string
@param gestures: A list of gestures associated with this script
@type gestures: list(string)
@param canPropagate: Whether this script should also apply when it belongs to a focus ancestor object.
@type canPropagate: bool
@param bypassInputHelp: Whether this script should run when input help is active.
@type bypassInputHelp: bool
@param resumeSayAllMode: The say all mode that should be resumed when active before executing this script.
One of the C{sayAllHandler.CURSOR_*} constants.
@type resumeSayAllMode: int
"""
if gestures is None:
gestures = []
def script_decorator(decoratedScript):
# Scripts are unbound instance methods in python 2 and functions in python 3.
# Therefore, we use inspect.isroutine to check whether a script is either a function or instance method.
if not inspect.isroutine(decoratedScript):
log.warning(
"Using the script decorator is unsupported for %r" % decoratedScript,
stack_info=True
)
return decoratedScript
if not decoratedScript.__name__.startswith("script_"):
log.warning(
"Can't apply script decorator to %r which name does not start with 'script_'" % decoratedScript.__name__,
stack_info=True
)
return decoratedScript
decoratedScript.__doc__ = description
if category is not None:
decoratedScript.category = category
if gesture is not None:
gestures.append(gesture)
if gestures:
decoratedScript.gestures = gestures
decoratedScript.canPropagate = canPropagate
decoratedScript.bypassInputHelp = bypassInputHelp
if resumeSayAllMode is not None:
decoratedScript.resumeSayAllMode = resumeSayAllMode
return decoratedScript
return script_decorator