forked from nvaccess/nvda
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.py
440 lines (396 loc) · 16.4 KB
/
api.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
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2021 NV Access Limited, James Teh, Michael Curran, Peter Vagner, Derek Riemer,
# Davy Kager, Babbage B.V., Leonard de Ruijter, Joseph Lee, Accessolutions, Julien Cochuyt
# This file may be used under the terms of the GNU General Public License, version 2 or later.
# For more details see: https://www.gnu.org/licenses/gpl-2.0.html
"""General functions for NVDA"""
import config
import textInfos
import review
import globalVars
from logHandler import log
import ui
import treeInterceptorHandler
import NVDAObjects
import winUser
import controlTypes
import eventHandler
import braille
import vision
import watchdog
import exceptions
import appModuleHandler
import cursorManager
from typing import Any, Optional
#User functions
def getFocusObject() -> NVDAObjects.NVDAObject:
"""
Gets the current object with focus.
@returns: the object with focus
"""
return globalVars.focusObject
def getForegroundObject():
"""Gets the current foreground object.
This (cached) object is the (effective) top-level "window" (hwnd).
EG a Dialog rather than the focused control within the dialog.
The cache is updated as queued events are processed, as such there will be a delay between the winEvent
and this function matching. However, within NVDA this should be used in order to be in sync with other
functions such as "getFocusAncestors".
@returns: the current foreground object
@rtype: L{NVDAObjects.NVDAObject}
"""
return globalVars.foregroundObject
def setForegroundObject(obj):
"""Stores the given object as the current foreground object.
Note: does not cause the operating system to change the foreground window,
but simply allows NVDA to keep track of what the foreground window is.
Alternative names for this function may have been:
- setLastForegroundWindow
- setLastForegroundEventObject
@param obj: the object that will be stored as the current foreground object
@type obj: NVDAObjects.NVDAObject
"""
if not isinstance(obj,NVDAObjects.NVDAObject):
return False
globalVars.foregroundObject=obj
return True
def setFocusObject(obj):
"""Stores an object as the current focus object. (Note: this does not physically change the window with focus in the operating system, but allows NVDA to keep track of the correct object).
Before overriding the last object, this function calls event_loseFocus on the object to notify it that it is loosing focus.
@param obj: the object that will be stored as the focus object
@type obj: NVDAObjects.NVDAObject
"""
if not isinstance(obj,NVDAObjects.NVDAObject):
return False
if globalVars.focusObject:
eventHandler.executeEvent("loseFocus",globalVars.focusObject)
oldFocusLine=globalVars.focusAncestors
#add the old focus to the old focus ancestors, but only if its not None (is none at NVDA initialization)
if globalVars.focusObject:
oldFocusLine.append(globalVars.focusObject)
oldAppModules=[o.appModule for o in oldFocusLine if o and o.appModule]
appModuleHandler.cleanup()
ancestors=[]
tempObj=obj
matchedOld=False
focusDifferenceLevel=0
oldFocusLineLength=len(oldFocusLine)
# Starting from the focus, move up the ancestor chain.
safetyCount=0
while tempObj:
if safetyCount<100:
safetyCount+=1
else:
try:
log.error(
"Never ending focus ancestry:"
f" last object: {tempObj.name}, {controlTypes.Role(tempObj.role).displayString},"
f" window class {tempObj.windowClassName}, application name {tempObj.appModule.appName}"
)
except:
pass
tempObj=getDesktopObject()
# Scan backwards through the old ancestors looking for a match.
for index in range(oldFocusLineLength-1,-1,-1):
watchdog.alive()
if tempObj==oldFocusLine[index]:
# Match! The old and new focus ancestors converge at this point.
# Copy the old ancestors up to and including this object.
origAncestors=oldFocusLine[0:index+1]
#make sure to cache the last old ancestor as a parent on the first new ancestor so as not to leave a broken parent cache
if ancestors and origAncestors:
ancestors[0].container=origAncestors[-1]
origAncestors.extend(ancestors)
ancestors=origAncestors
focusDifferenceLevel=index+1
# We don't need to process any more in either this loop or the outer loop; we have all of the ancestors.
matchedOld=True
break
if matchedOld:
break
# We're moving backwards along the ancestor chain, so add this to the start of the list.
ancestors.insert(0,tempObj)
container=tempObj.container
tempObj.container=container # Cache the parent.
tempObj=container
#Remove the final new ancestor as this will be the new focus object
del ancestors[-1]
# #5467: Ensure that the appModule of the real focus is included in the newAppModule list for profile switching
# Rather than an original focus ancestor which happened to match the new focus.
newAppModules=[o.appModule for o in ancestors if o and o.appModule]
if obj.appModule:
newAppModules.append(obj.appModule)
try:
treeInterceptorHandler.cleanup()
except exceptions.CallCancelled:
pass
treeInterceptorObject=None
o=None
watchdog.alive()
for o in ancestors[focusDifferenceLevel:]+[obj]:
try:
treeInterceptorObject=treeInterceptorHandler.update(o)
except:
log.error("Error updating tree interceptor", exc_info=True)
#Always make sure that the focus object's treeInterceptor is forced to either the found treeInterceptor (if its in it) or to None
#This is to make sure that the treeInterceptor does not have to be looked up, which can cause problems for winInputHook
if obj is o or obj in treeInterceptorObject:
obj.treeInterceptor=treeInterceptorObject
else:
obj.treeInterceptor=None
# #3804: handleAppSwitch should be called as late as possible,
# as triggers must not be out of sync with global focus variables.
# setFocusObject shouldn't fail earlier anyway, but it's best to be safe.
appModuleHandler.handleAppSwitch(oldAppModules,newAppModules)
# Set global focus variables.
globalVars.focusDifferenceLevel=focusDifferenceLevel
globalVars.focusObject=obj
globalVars.focusAncestors=ancestors
braille.invalidateCachedFocusAncestors(focusDifferenceLevel)
if config.conf["reviewCursor"]["followFocus"]:
setNavigatorObject(obj,isFocus=True)
return True
def getFocusDifferenceLevel():
return globalVars.focusDifferenceLevel
def getFocusAncestors():
"""An array of NVDAObjects that are all parents of the object which currently has focus"""
return globalVars.focusAncestors
def getMouseObject():
"""Returns the object that is directly under the mouse"""
return globalVars.mouseObject
def setMouseObject(obj):
"""Tells NVDA to remember the given object as the object that is directly under the mouse"""
globalVars.mouseObject=obj
def getDesktopObject():
"""Get the desktop object"""
return globalVars.desktopObject
def setDesktopObject(obj):
"""Tells NVDA to remember the given object as the desktop object"""
globalVars.desktopObject=obj
def getReviewPosition() -> textInfos.TextInfo:
"""Retrieves the current TextInfo instance representing the user's review position.
If it is not set, it uses navigator object to create a TextInfo.
"""
if globalVars.reviewPosition:
return globalVars.reviewPosition
else:
obj=globalVars.navigatorObject
globalVars.reviewPosition,globalVars.reviewPositionObj=review.getPositionForCurrentMode(obj)
return globalVars.reviewPosition
def setReviewPosition(
reviewPosition,
clearNavigatorObject=True,
isCaret=False,
isMouse=False
):
"""Sets a TextInfo instance as the review position.
@param clearNavigatorObject: if true, It sets the current navigator object to C{None}.
In that case, the next time the navigator object is asked for it fetches it from the review position.
@type clearNavigatorObject: bool
@param isCaret: Whether the review position is changed due to caret following.
@type isCaret: bool
@param isMouse: Whether the review position is changed due to mouse following.
@type isMouse: bool
"""
globalVars.reviewPosition=reviewPosition.copy()
globalVars.reviewPositionObj=reviewPosition.obj
if clearNavigatorObject: globalVars.navigatorObject=None
# When the review cursor follows the caret and braille is auto tethered to review,
# we should not update braille with the new review position as a tether to focus is due.
if not (braille.handler.shouldAutoTether and isCaret):
braille.handler.handleReviewMove(shouldAutoTether=not isCaret)
if isCaret:
visionContext = vision.constants.Context.CARET
elif isMouse:
visionContext = vision.constants.Context.MOUSE
else:
visionContext = vision.constants.Context.REVIEW
vision.handler.handleReviewMove(context=visionContext)
def getNavigatorObject():
"""Gets the current navigator object. Navigator objects can be used to navigate around the operating system (with the number pad) with out moving the focus. If the navigator object is not set, it fetches it from the review position.
@returns: the current navigator object
@rtype: L{NVDAObjects.NVDAObject}
"""
if globalVars.navigatorObject:
return globalVars.navigatorObject
else:
if review.getCurrentMode()=='object':
obj=globalVars.reviewPosition.obj
else:
try:
obj=globalVars.reviewPosition.NVDAObjectAtStart
except (NotImplementedError,LookupError):
obj=globalVars.reviewPosition.obj
globalVars.navigatorObject=getattr(obj,'rootNVDAObject',None) or obj
return globalVars.navigatorObject
def setNavigatorObject(obj,isFocus=False):
"""Sets an object to be the current navigator object. Navigator objects can be used to navigate around the operating system (with the number pad) with out moving the focus. It also sets the current review position to None so that next time the review position is asked for, it is created from the navigator object.
@param obj: the object that will be set as the current navigator object
@type obj: NVDAObjects.NVDAObject
@param isFocus: true if the navigator object was set due to a focus change.
@type isFocus: bool
"""
if not isinstance(obj,NVDAObjects.NVDAObject):
return False
globalVars.navigatorObject=obj
oldPos=globalVars.reviewPosition
oldPosObj=globalVars.reviewPositionObj
globalVars.reviewPosition=None
globalVars.reviewPositionObj=None
reviewMode=review.getCurrentMode()
# #3320: If in document review yet there is no document to review the mode should be forced to object.
if reviewMode=='document' and (not isinstance(obj.treeInterceptor,treeInterceptorHandler.DocumentTreeInterceptor) or not obj.treeInterceptor.isReady or obj.treeInterceptor.passThrough):
review.setCurrentMode('object',False)
elif isinstance(obj.treeInterceptor,treeInterceptorHandler.DocumentTreeInterceptor) and obj.treeInterceptor.isReady and not obj.treeInterceptor.passThrough:
if reviewMode=='object':
review.setCurrentMode('document',False)
if isFocus:
globalVars.reviewPosition=obj.treeInterceptor.makeTextInfo(textInfos.POSITION_CARET)
globalVars.reviewPositionObj=globalVars.reviewPosition
eventHandler.executeEvent("becomeNavigatorObject",obj,isFocus=isFocus)
def isTypingProtected():
"""Checks to see if key echo should be suppressed because the focus is currently on an object that has its protected state set.
@returns: True if it should be suppressed, False otherwise.
@rtype: boolean
"""
focusObject=getFocusObject()
if focusObject and focusObject.isProtected:
return True
else:
return False
def createStateList(states):
"""Breaks down the given integer in to a list of numbers that are 2 to the power of their position."""
return [x for x in [1<<y for y in range(32)] if x&states]
def moveMouseToNVDAObject(obj):
"""Moves the mouse to the given NVDA object's position"""
location=obj.location
if location:
winUser.setCursorPos(*location.center)
def processPendingEvents(processEventQueue=True):
# Import late to avoid circular import.
import IAccessibleHandler
import JABHandler
import wx
import queueHandler
watchdog.alive()
wx.Yield()
JABHandler.pumpAll()
IAccessibleHandler.pumpAll()
import baseObject
baseObject.AutoPropertyObject.invalidateCaches()
if processEventQueue:
queueHandler.flushQueue(queueHandler.eventQueue)
def copyToClip(text: str, notify: Optional[bool] = False) -> bool:
"""Copies the given text to the windows clipboard.
@returns: True if it succeeds, False otherwise.
@param text: the text which will be copied to the clipboard
@param notify: whether to emit a confirmation message
"""
if not isinstance(text, str) or len(text) == 0:
return False
import gui
try:
with winUser.openClipboard(gui.mainFrame.Handle):
winUser.emptyClipboard()
winUser.setClipboardData(winUser.CF_UNICODETEXT, text)
got = getClipData()
except OSError:
if notify:
ui.reportTextCopiedToClipboard() # No argument reports a failure.
return False
if got == text:
if notify:
ui.reportTextCopiedToClipboard(text)
return True
if notify:
ui.reportTextCopiedToClipboard() # No argument reports a failure.
return False
def getClipData():
"""Receives text from the windows clipboard.
@returns: Clipboard text
@rtype: string
"""
import gui
with winUser.openClipboard(gui.mainFrame.Handle):
return winUser.getClipboardData(winUser.CF_UNICODETEXT) or u""
def getStatusBar():
"""Obtain the status bar for the current foreground object.
@return: The status bar object or C{None} if no status bar was found.
@rtype: L{NVDAObjects.NVDAObject}
"""
foreground = getForegroundObject()
try:
return foreground.appModule.statusBar
except NotImplementedError:
pass
# The status bar is usually at the bottom of the screen.
# Therefore, get the object at the bottom left of the foreground object using screen coordinates.
location=foreground.location
if not location:
return None
left, top, width, height = location
bottom = top + height - 1
obj = getDesktopObject().objectFromPoint(left, bottom)
# We may have landed in a child of the status bar, so search the ancestry for a status bar.
while obj and not obj.role == controlTypes.Role.STATUSBAR:
obj = obj.parent
return obj
def getStatusBarText(obj):
"""Get the text from a status bar.
This includes the name of the status bar and the names and values of all of its children.
@param obj: The status bar.
@type obj: L{NVDAObjects.NVDAObject}
@return: The status bar text.
@rtype: str
"""
try:
return obj.appModule.getStatusBarText(obj)
except NotImplementedError:
pass
text = obj.name or ""
if text:
text += " "
return text + " ".join(chunk for child in obj.children for chunk in (child.name, child.value) if chunk and isinstance(chunk, str) and not chunk.isspace())
def filterFileName(name):
"""Replaces invalid characters in a given string to make a windows compatible file name.
@param name: The file name to filter.
@type name: str
@returns: The filtered file name.
@rtype: str
"""
invalidChars=':?*\|<>/"'
for c in invalidChars:
name=name.replace(c,'_')
return name
def isNVDAObject(obj: Any) -> bool:
"""Returns whether the supplied object is a L{NVDAObjects.NVDAObject}"""
return isinstance(obj, NVDAObjects.NVDAObject)
def isCursorManager(obj: Any) -> bool:
"""Returns whether the supplied object is a L{cursorManager.CursorManager}"""
return isinstance(obj, cursorManager.CursorManager)
def isTreeInterceptor(obj: Any) -> bool:
"""Returns whether the supplied object is a L{treeInterceptorHandler.TreeInterceptor}"""
return isinstance(obj, treeInterceptorHandler.TreeInterceptor)
def isObjectInActiveTreeInterceptor(obj: NVDAObjects.NVDAObject) -> bool:
"""Returns whether the supplied L{NVDAObjects.NVDAObject} is
in an active L{treeInterceptorHandler.TreeInterceptor},
i.e. a tree interceptor that is not in pass through mode.
"""
return bool(
isinstance(obj, NVDAObjects.NVDAObject)
and obj.treeInterceptor
and not obj.treeInterceptor.passThrough
)
def getCaretObject():
"""Gets the object which contains the caret.
This is normally the focus object.
However, if the focus object has a tree interceptor which is not in focus mode,
the tree interceptor will be returned.
@return: The object containing the caret.
@rtype: L{baseObject.ScriptableObject}
"""
obj = getFocusObject()
ti = obj.treeInterceptor
if isinstance(ti,treeInterceptorHandler.DocumentTreeInterceptor) and ti.isReady and not ti.passThrough:
return ti
return obj