core.py
15.4 KB
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
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# -*- coding: UTF-8 -*-
#core.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2016 NV Access Limited, Aleksey Sadovoy, Christopher Toth, Joseph Lee, Peter Vágner
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
"""NVDA core"""
# Do this first to initialise comtypes.client.gen_dir and the comtypes.gen search path.
import comtypes.client
# Append our comInterfaces directory to the comtypes.gen search path.
import comtypes.gen
import comInterfaces
comtypes.gen.__path__.append(comInterfaces.__path__[0])
#Apply several monky patches to comtypes
import comtypesMonkeyPatches
import sys
import winVersion
import thread
import nvwave
import os
import time
import ctypes
import logHandler
import globalVars
from logHandler import log
import addonHandler
PUMP_MAX_DELAY = 10
#: The thread identifier of the main thread.
mainThreadId = thread.get_ident()
_pump = None
_isPumpPending = False
def doStartupDialogs():
import config
import gui
# Translators: The title of the dialog to tell users that there are erros in the configuration file.
if config.conf.baseConfigError:
import wx
gui.messageBox(
# Translators: A message informing the user that there are errors in the configuration file.
_("Your configuration file contains errors. "
"Your configuration has been reset to factory defaults.\n"
"More details about the errors can be found in the log file."),
# Translators: The title of the dialog to tell users that there are errors in the configuration file.
_("Configuration File Error"),
wx.OK | wx.ICON_EXCLAMATION)
if config.conf["general"]["showWelcomeDialogAtStartup"]:
gui.WelcomeDialog.run()
if config.conf["speechViewer"]["showSpeechViewerAtStartup"]:
gui.mainFrame.onToggleSpeechViewerCommand(evt=None)
import inputCore
if inputCore.manager.userGestureMap.lastUpdateContainedError:
import wx
gui.messageBox(_("Your gesture map file contains errors.\n"
"More details about the errors can be found in the log file."),
_("gesture map File Error"), wx.OK|wx.ICON_EXCLAMATION)
def restart(disableAddons=False):
"""Restarts NVDA by starting a new copy with -r."""
if globalVars.appArgs.launcher:
import wx
globalVars.exitCode=3
wx.GetApp().ExitMainLoop()
return
import subprocess
import winUser
import shellapi
options=[]
if "-r" not in sys.argv:
options.append("-r")
try:
sys.argv.remove('--disable-addons')
except ValueError:
pass
if disableAddons:
options.append('--disable-addons')
try:
sys.argv.remove("--ease-of-access")
except ValueError:
pass
shellapi.ShellExecute(None, None,
sys.executable.decode("mbcs"),
subprocess.list2cmdline(sys.argv + options).decode("mbcs"),
None,
# #4475: ensure that the first window of the new process is not hidden by providing SW_SHOWNORMAL
winUser.SW_SHOWNORMAL)
def resetConfiguration(factoryDefaults=False):
"""Loads the configuration, installs the correct language support and initialises audio so that it will use the configured synth and speech settings.
"""
import config
import braille
import speech
import languageHandler
import inputCore
log.debug("Terminating braille")
braille.terminate()
log.debug("terminating speech")
speech.terminate()
log.debug("terminating addonHandler")
addonHandler.terminate()
log.debug("Reloading config")
config.conf.reset(factoryDefaults=factoryDefaults)
logHandler.setLogLevelFromConfig()
#Language
lang = config.conf["general"]["language"]
log.debug("setting language to %s"%lang)
languageHandler.setLanguage(lang)
# Addons
addonHandler.initialize()
#Speech
log.debug("initializing speech")
speech.initialize()
#braille
log.debug("Initializing braille")
braille.initialize()
log.debug("Reloading user and locale input gesture maps")
inputCore.manager.loadUserGestureMap()
inputCore.manager.loadLocaleGestureMap()
import audioDucking
if audioDucking.isAudioDuckingSupported():
audioDucking.handleConfigProfileSwitch()
log.info("Reverted to saved configuration")
def _setInitialFocus():
"""Sets the initial focus if no focus event was received at startup.
"""
import eventHandler
import api
if eventHandler.lastQueuedFocusObject:
# The focus has already been set or a focus event is pending.
return
try:
focus = api.getDesktopObject().objectWithFocus()
if focus:
eventHandler.queueEvent('gainFocus', focus)
except:
log.exception("Error retrieving initial focus")
def main():
"""NVDA's core main loop.
This initializes all modules such as audio, IAccessible, keyboard, mouse, and GUI. Then it initialises the wx application object and sets up the core pump, which checks the queues and executes functions when requested. Finally, it starts the wx main loop.
"""
log.debug("Core starting")
try:
# Windows >= Vista
ctypes.windll.user32.SetProcessDPIAware()
except AttributeError:
pass
import config
if not globalVars.appArgs.configPath:
globalVars.appArgs.configPath=config.getUserDefaultConfigPath(useInstalledPathIfExists=globalVars.appArgs.launcher)
#Initialize the config path (make sure it exists)
config.initConfigPath()
log.info("Config dir: %s"%os.path.abspath(globalVars.appArgs.configPath))
log.debug("loading config")
import config
config.initialize()
if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
try:
nvwave.playWaveFile("waves\\start.wav")
except:
pass
logHandler.setLogLevelFromConfig()
try:
lang = config.conf["general"]["language"]
import languageHandler
log.debug("setting language to %s"%lang)
languageHandler.setLanguage(lang)
except:
log.warning("Could not set language to %s"%lang)
import versionInfo
log.info("NVDA version %s" % versionInfo.version)
log.info("Using Windows version %s" % winVersion.winVersionText)
log.info("Using Python version %s"%sys.version)
log.info("Using comtypes version %s"%comtypes.__version__)
# Set a reasonable timeout for any socket connections NVDA makes.
import socket
socket.setdefaulttimeout(10)
log.debug("Initializing add-ons system")
addonHandler.initialize()
if globalVars.appArgs.disableAddons:
log.info("Add-ons are disabled. Restart NVDA to enable them.")
import appModuleHandler
log.debug("Initializing appModule Handler")
appModuleHandler.initialize()
import NVDAHelper
log.debug("Initializing NVDAHelper")
NVDAHelper.initialize()
import speechDictHandler
log.debug("Speech Dictionary processing")
speechDictHandler.initialize()
import speech
log.debug("Initializing speech")
speech.initialize()
if not globalVars.appArgs.minimal and (time.time()-globalVars.startTime)>5:
log.debugWarning("Slow starting core (%.2f sec)" % (time.time()-globalVars.startTime))
# Translators: This is spoken when NVDA is starting.
speech.speakMessage(_("Loading NVDA. Please wait..."))
import wx
log.info("Using wx version %s"%wx.version())
class App(wx.App):
def OnAssert(self,file,line,cond,msg):
message="{file}, line {line}:\nassert {cond}: {msg}".format(file=file,line=line,cond=cond,msg=msg)
log.debugWarning(message,codepath="WX Widgets",stack_info=True)
app = App(redirect=False)
# We do support QueryEndSession events, but we don't want to do anything for them.
app.Bind(wx.EVT_QUERY_END_SESSION, lambda evt: None)
def onEndSession(evt):
# NVDA will be terminated as soon as this function returns, so save configuration if appropriate.
config.saveOnExit()
speech.cancelSpeech()
if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
try:
nvwave.playWaveFile("waves\\exit.wav",async=False)
except:
pass
log.info("Windows session ending")
app.Bind(wx.EVT_END_SESSION, onEndSession)
import braille
log.debug("Initializing braille")
braille.initialize()
log.debug("Initializing braille input")
import brailleInput
brailleInput.initialize()
import displayModel
log.debug("Initializing displayModel")
displayModel.initialize()
log.debug("Initializing GUI")
import gui
gui.initialize()
import audioDucking
if audioDucking.isAudioDuckingSupported():
# the GUI mainloop must be running for this to work so delay it
wx.CallAfter(audioDucking.initialize)
# #3763: In wxPython 3, the class name of frame windows changed from wxWindowClassNR to wxWindowNR.
# NVDA uses the main frame to check for and quit another instance of NVDA.
# To remain compatible with older versions of NVDA, create our own wxWindowClassNR.
# We don't need to do anything else because wx handles WM_QUIT for all windows.
import windowUtils
class MessageWindow(windowUtils.CustomWindow):
className = u"wxWindowClassNR"
messageWindow = MessageWindow(unicode(versionInfo.name))
# initialize wxpython localization support
locale = wx.Locale()
lang=languageHandler.getLanguage()
wxLang=locale.FindLanguageInfo(lang)
if not wxLang and '_' in lang:
wxLang=locale.FindLanguageInfo(lang.split('_')[0])
if hasattr(sys,'frozen'):
locale.AddCatalogLookupPathPrefix(os.path.join(os.getcwdu(),"locale"))
if wxLang:
try:
locale.Init(wxLang.Language)
except:
log.error("Failed to initialize wx locale",exc_info=True)
else:
log.debugWarning("wx does not support language %s" % lang)
import api
import winUser
import NVDAObjects.window
desktopObject=NVDAObjects.window.Window(windowHandle=winUser.getDesktopWindow())
api.setDesktopObject(desktopObject)
api.setFocusObject(desktopObject)
api.setNavigatorObject(desktopObject)
api.setMouseObject(desktopObject)
import JABHandler
log.debug("initializing Java Access Bridge support")
try:
JABHandler.initialize()
except NotImplementedError:
log.warning("Java Access Bridge not available")
except:
log.error("Error initializing Java Access Bridge support", exc_info=True)
import winConsoleHandler
log.debug("Initializing winConsole support")
winConsoleHandler.initialize()
import UIAHandler
log.debug("Initializing UIA support")
try:
UIAHandler.initialize()
except NotImplementedError:
log.warning("UIA not available")
except:
log.error("Error initializing UIA support", exc_info=True)
import IAccessibleHandler
log.debug("Initializing IAccessible support")
IAccessibleHandler.initialize()
log.debug("Initializing input core")
import inputCore
inputCore.initialize()
import keyboardHandler
log.debug("Initializing keyboard handler")
keyboardHandler.initialize()
import mouseHandler
log.debug("initializing mouse handler")
mouseHandler.initialize()
import touchHandler
log.debug("Initializing touchHandler")
try:
touchHandler.initialize()
except NotImplementedError:
pass
import globalPluginHandler
log.debug("Initializing global plugin handler")
globalPluginHandler.initialize()
if globalVars.appArgs.install or globalVars.appArgs.installSilent:
import wx
import gui.installerGui
wx.CallAfter(gui.installerGui.doSilentInstall,startAfterInstall=not globalVars.appArgs.installSilent)
elif not globalVars.appArgs.minimal:
try:
# Translators: This is shown on a braille display (if one is connected) when NVDA starts.
braille.handler.message(_("NVDA started"))
except:
log.error("", exc_info=True)
if globalVars.appArgs.launcher:
gui.LauncherDialog.run()
# LauncherDialog will call doStartupDialogs() afterwards if required.
else:
wx.CallAfter(doStartupDialogs)
import queueHandler
# Queue the handling of initial focus,
# as API handlers might need to be pumped to get the first focus event.
queueHandler.queueFunction(queueHandler.eventQueue, _setInitialFocus)
import watchdog
import baseObject
# Doing this here is a bit ugly, but we don't want these modules imported
# at module level, including wx.
log.debug("Initializing core pump")
class CorePump(wx.Timer):
"Checks the queues and executes functions."
def Notify(self):
global _isPumpPending
_isPumpPending = False
watchdog.alive()
try:
if touchHandler.handler:
touchHandler.handler.pump()
JABHandler.pumpAll()
IAccessibleHandler.pumpAll()
queueHandler.pumpAll()
mouseHandler.pumpAll()
braille.pumpAll()
except:
log.exception("errors in this core pump cycle")
baseObject.AutoPropertyObject.invalidateCaches()
watchdog.asleep()
if _isPumpPending and not _pump.IsRunning():
# #3803: A pump was requested, but the timer was ignored by a modal loop
# because timers aren't re-entrant.
# Therefore, schedule another pump.
_pump.Start(PUMP_MAX_DELAY, True)
global _pump
_pump = CorePump()
requestPump()
log.debug("Initializing watchdog")
watchdog.initialize()
try:
import updateCheck
except RuntimeError:
updateCheck=None
log.debug("Update checking not supported")
else:
log.debug("initializing updateCheck")
updateCheck.initialize()
log.info("NVDA initialized")
log.debug("entering wx application main loop")
app.MainLoop()
log.info("Exiting")
if updateCheck:
_terminate(updateCheck)
_terminate(watchdog)
_terminate(globalPluginHandler, name="global plugin handler")
_terminate(gui)
config.saveOnExit()
try:
if globalVars.focusObject and hasattr(globalVars.focusObject,"event_loseFocus"):
log.debug("calling lose focus on object with focus")
globalVars.focusObject.event_loseFocus()
except:
log.exception("Lose focus error")
try:
speech.cancelSpeech()
except:
pass
import treeInterceptorHandler
_terminate(treeInterceptorHandler)
_terminate(IAccessibleHandler, name="IAccessible support")
_terminate(UIAHandler, name="UIA support")
_terminate(winConsoleHandler, name="winConsole support")
_terminate(JABHandler, name="Java Access Bridge support")
_terminate(appModuleHandler, name="app module handler")
_terminate(NVDAHelper)
_terminate(touchHandler)
_terminate(keyboardHandler, name="keyboard handler")
_terminate(mouseHandler)
_terminate(inputCore)
_terminate(brailleInput)
_terminate(braille)
_terminate(speech)
_terminate(addonHandler)
if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
try:
nvwave.playWaveFile("waves\\exit.wav",async=False)
except:
pass
# #5189: Destroy the message window as late as possible
# so new instances of NVDA can find this one even if it freezes during exit.
messageWindow.destroy()
log.debug("core done")
def _terminate(module, name=None):
if name is None:
name = module.__name__
log.debug("Terminating %s" % name)
try:
module.terminate()
except:
log.exception("Error terminating %s" % name)
def requestPump():
"""Request a core pump.
This will perform any queued activity.
It is delayed slightly so that queues can implement rate limiting,
filter extraneous events, etc.
"""
global _isPumpPending
if not _pump or _isPumpPending:
return
_isPumpPending = True
if thread.get_ident() == mainThreadId:
_pump.Start(PUMP_MAX_DELAY, True)
return
# This isn't the main thread. wx timers cannot be run outside the main thread.
# Therefore, Have wx start it in the main thread with a CallAfter.
import wx
wx.CallAfter(_pump.Start,PUMP_MAX_DELAY, True)
def callLater(delay, callable, *args, **kwargs):
"""Call a callable once after the specified number of milliseconds.
This is currently a thin wrapper around C{wx.CallLater},
but this should be used instead for calls which aren't just for UI,
as it notifies watchdog appropriately.
This function can be safely called from any thread.
"""
import wx
if thread.get_ident() == mainThreadId:
return wx.CallLater(delay, _callLaterExec, callable, args, kwargs)
else:
return wx.CallAfter(wx.CallLater,delay, _callLaterExec, callable, args, kwargs)
def _callLaterExec(callable, args, kwargs):
import watchdog
watchdog.alive()
try:
return callable(*args, **kwargs)
finally:
watchdog.asleep()