-
-
Notifications
You must be signed in to change notification settings - Fork 766
Expand file tree
/
Copy path__init__.py
More file actions
1061 lines (912 loc) · 40.7 KB
/
__init__.py
File metadata and controls
1061 lines (912 loc) · 40.7 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
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2026 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Mesar Hameed, Joseph Lee,
# Thomas Stivers, Babbage B.V., Accessolutions, Julien Cochuyt, Cyrille Bougot, Luke Davis
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
from collections.abc import Callable
import os
import warnings
import wx
import wx.adv
import wx.lib.agw.persist
import winBindings.kernel32
import globalVars
import tones
import ui
from documentationUtils import getDocFilePath, displayLicense, reportNoDocumentation
from logHandler import log
import config
import buildVersion
import versionInfo
import speech
import queueHandler
import core
from typing import Any
import systemUtils
from .message import (
Button,
Payload,
# messageBox is accessed through `gui.messageBox` as opposed to `gui.message.messageBox` throughout NVDA,
# be cautious when removing
messageBox,
MessageDialog,
displayDialogAsModal,
)
from . import blockAction
from .speechDict import (
DefaultDictionaryDialog,
VoiceDictionaryDialog,
TemporaryDictionaryDialog,
)
from .nvdaControls import _ContinueCancelDialog
# ExitDialog is accessed through `import gui.ExitDialog` as opposed to `gui.exit.ExitDialog`.
# Be careful when removing, and only do in a compatibility breaking release.
from .exit import ExitDialog
from .settingsDialogs import (
AddonStorePanel,
AdvancedPanel,
AudioPanel,
BrailleDisplaySelectionDialog,
BrailleSettingsPanel,
BrowseModePanel,
DocumentFormattingPanel,
GeneralSettingsPanel,
InputCompositionPanel,
KeyboardSettingsPanel,
MagnifierPanel,
MouseSettingsPanel,
MultiCategorySettingsDialog,
NVDASettingsDialog,
ObjectPresentationPanel,
PrivacyAndSecuritySettingsPanel,
RemoteSettingsPanel,
ReviewCursorPanel,
SettingsDialog,
SpeechSettingsPanel,
SpeechSymbolsDialog,
SynthesizerSelectionDialog,
TouchInteractionPanel,
UwpOcrPanel,
VisionSettingsPanel,
)
from .startupDialogs import WelcomeDialog
from .inputGestures import InputGesturesDialog
from . import logViewer
import speechViewer
import winUser
import api
import NVDAState
if NVDAState._allowDeprecatedAPI():
def quit():
"""
Deprecated, use `wx.CallAfter(mainFrame.onExitCommand, None)` directly instead.
"""
log.debugWarning("Deprecated function called: gui.quit", stack_info=True)
wx.CallAfter(mainFrame.onExitCommand, None)
try:
import updateCheck
except RuntimeError:
updateCheck = None
### Constants
NVDA_PATH = globalVars.appDir
ICON_PATH = os.path.join(NVDA_PATH, "images", "nvda.ico")
DONATE_URL = f"{buildVersion.url}/donate/"
### Globals
mainFrame: "MainFrame | None" = None
"""Set by initialize. Should be used as the parent for "top level" dialogs.
"""
def __getattr__(attrName: str) -> Any:
"""Module level `__getattr__` used to preserve backward compatibility."""
from gui.settingsDialogs import AutoSettingsMixin, SettingsPanel
if attrName == "AutoSettingsMixin" and NVDAState._allowDeprecatedAPI():
log.warning(
"Importing AutoSettingsMixin from here is deprecated. "
"Import AutoSettingsMixin from gui.settingsDialogs instead. ",
# Include stack info so testers can report warning to add-on author.
stack_info=True,
)
return AutoSettingsMixin
if attrName == "SettingsPanel" and NVDAState._allowDeprecatedAPI():
log.warning(
"Importing SettingsPanel from here is deprecated. "
"Import SettingsPanel from gui.settingsDialogs instead. ",
# Include stack info so testers can report warning to add-on author.
stack_info=True,
)
return SettingsPanel
if attrName == "ExecAndPump" and NVDAState._allowDeprecatedAPI():
log.warning(
"Importing ExecAndPump from here is deprecated. Import ExecAndPump from systemUtils instead. ",
# Include stack info so testers can report warning to add-on author.
stack_info=True,
)
import systemUtils
return systemUtils.ExecAndPump
raise AttributeError(f"module {repr(__name__)} has no attribute {repr(attrName)}")
class MainFrame(wx.Frame):
"""A hidden window, intended to act as the parent to all dialogs."""
def __init__(self):
style = wx.DEFAULT_FRAME_STYLE ^ wx.MAXIMIZE_BOX ^ wx.MINIMIZE_BOX | wx.FRAME_NO_TASKBAR
super().__init__(None, wx.ID_ANY, buildVersion.name, size=(1, 1), style=style)
self.Bind(wx.EVT_CLOSE, self.onExitCommand)
self.sysTrayIcon = SysTrayIcon(self)
#: The focus before the last popup or C{None} if unknown.
#: This is only valid before L{prePopup} is called,
#: so it should be used as early as possible in any popup that needs it.
#: @type: L{NVDAObject}
self.prevFocus = None
#: The focus ancestors before the last popup or C{None} if unknown.
#: @type: list of L{NVDAObject}
self.prevFocusAncestors = None
# If NVDA has the uiAccess privilege, it can always set the foreground window.
if not systemUtils.hasUiAccess():
# This makes Windows return to the previous foreground window and also seems to allow NVDA to be brought to the foreground.
self.Show()
self.Hide()
if winUser.isWindowVisible(self.Handle):
# HACK: Work around a wx bug where Hide() doesn't actually hide the window,
# but IsShown() returns False and Hide() again doesn't fix it.
# This seems to happen if the call takes too long.
self.Show()
self.Hide()
def prePopup(self):
"""Prepare for a popup.
This should be called before any dialog or menu which should pop up for the user.
L{postPopup} should be called after the dialog or menu has been shown.
@postcondition: A dialog or menu may be shown.
"""
focus = api.getFocusObject()
# Do not set prevFocus if the focus is on a control rendered by NVDA itself, such as the NVDA menu.
# This allows to refer to the control that had focus before opening the menu while still using NVDA
# on its own controls.
# The check for NVDA process ID can be bypassed by setting the optional attribute
# L{isPrevFocusOnNvdaPopup} to L{True} when a NVDA dialog offers customizable bound gestures,
# eg. the NVDA Python Console.
if focus.processID != globalVars.appPid or getattr(focus, "isPrevFocusOnNvdaPopup", False):
self.prevFocus = focus
self.prevFocusAncestors = api.getFocusAncestors()
if winUser.getWindowThreadProcessID(winUser.getForegroundWindow())[0] != globalVars.appPid:
# This process is not the foreground process, so bring it to the foreground.
self.Raise()
def postPopup(self):
"""Clean up after a popup dialog or menu.
This should be called after a dialog or menu was popped up for the user.
"""
self.prevFocus = None
self.prevFocusAncestors = None
if not winUser.isWindowVisible(winUser.getForegroundWindow()):
# The current foreground window is invisible, so we want to return to the previous foreground window.
# Showing and hiding our main window seems to achieve this.
self.Show()
self.Hide()
def showGui(self):
# The menu pops up at the location of the mouse, which means it pops up at an unpredictable location.
# Therefore, move the mouse to the center of the screen so that the menu will always pop up there.
location = api.getDesktopObject().location
winUser.setCursorPos(*location.center)
self.sysTrayIcon.onActivate(None)
def onRevertToSavedConfigurationCommand(self, evt):
queueHandler.queueFunction(queueHandler.eventQueue, core.resetConfiguration)
# Translators: Reported when last saved configuration has been applied by using revert to saved configuration option in NVDA menu.
queueHandler.queueFunction(queueHandler.eventQueue, ui.message, _("Configuration applied"))
@blockAction.when(blockAction.Context.MODAL_DIALOG_OPEN)
def _confirmRevertToDefaultConfiguration(self, evt):
"""Reset config to factory defaults, then show a dialog allowing the user to undo the reset.
This is used when triggered from the NVDA menu.
"""
from .configManagement import confirmRevertToDefaultConfiguration
confirmRevertToDefaultConfiguration()
@blockAction.when(blockAction.Context.MODAL_DIALOG_OPEN)
def onRevertToDefaultConfigurationCommand(self, evt):
"""Reset config to factory defaults without showing an undo dialog.
This is used for the keyboard shortcut triple-press recovery scenario.
"""
queueHandler.queueFunction(queueHandler.eventQueue, core.resetConfiguration, factoryDefaults=True)
queueHandler.queueFunction(
queueHandler.eventQueue,
ui.message,
# Translators: Reported when configuration has been restored to defaults,
# by using restore configuration to factory defaults item in NVDA menu.
_("Configuration restored to factory defaults"),
)
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.RUNNING_LAUNCHER,
)
def onSaveConfigurationCommand(self, evt):
try:
config.conf.save()
# Translators: Reported when current configuration has been saved.
queueHandler.queueFunction(queueHandler.eventQueue, ui.message, _("Configuration saved"))
except PermissionError:
messageBox(
# Translators: Message shown when current configuration cannot be saved,
# such as when running NVDA from a CD.
_("Could not save configuration - probably read only file system"),
# Translators: the title of an error message dialog
_("Error"),
wx.OK | wx.ICON_ERROR,
)
except Exception:
messageBox(
# Translators: Message shown when current configuration cannot be saved, for an unknown reason.
_("Could not save configuration; see the log for more details."),
# Translators: the title of an error message dialog
_("Error"),
wx.OK | wx.ICON_ERROR,
)
@blockAction.when(blockAction.Context.MODAL_DIALOG_OPEN)
def popupSettingsDialog(self, dialog: type[SettingsDialog], *args, **kwargs):
self.prePopup()
try:
dialog(self, *args, **kwargs).Show()
except SettingsDialog.MultiInstanceErrorWithDialog as errorWithDialog:
errorWithDialog.dialog.SetFocus()
except MultiCategorySettingsDialog.CategoryUnavailableError:
messageBox(
# Translators: Message shown when trying to open an unavailable category of a multi category settings dialog.
# Example: when trying to open touch interaction settings on an unsupported system.
_("The settings panel you tried to open is unavailable on this system."),
# Translators: the title of an error message dialog
_("Error"),
style=wx.OK | wx.ICON_ERROR,
)
self.postPopup()
if NVDAState._allowDeprecatedAPI():
def _popupSettingsDialog(self, dialog: type[SettingsDialog], *args, **kwargs):
log.warning(
"_popupSettingsDialog is deprecated, use popupSettingsDialog instead.",
stack_info=True,
)
self.popupSettingsDialog(dialog, *args, **kwargs)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onDefaultDictionaryCommand(self, evt):
self.popupSettingsDialog(DefaultDictionaryDialog)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onVoiceDictionaryCommand(self, evt):
self.popupSettingsDialog(VoiceDictionaryDialog)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onTemporaryDictionaryCommand(self, evt):
self.popupSettingsDialog(TemporaryDictionaryDialog)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onExecuteUpdateCommand(self, evt):
if updateCheck and updateCheck.isPendingUpdate():
destPath, version, apiVersion, backCompatToAPIVersion = updateCheck.getPendingUpdate()
from addonHandler import getIncompatibleAddons
if any(getIncompatibleAddons(apiVersion, backCompatToAPIVersion)):
confirmUpdateDialog = updateCheck.UpdateAskInstallDialog(
parent=mainFrame,
destPath=destPath,
version=version,
apiVersion=apiVersion,
backCompatTo=backCompatToAPIVersion,
)
runScriptModalDialog(confirmUpdateDialog, confirmUpdateDialog.callback)
else:
updateCheck.executePendingUpdate()
def evaluateUpdatePendingUpdateMenuItemCommand(self):
log.warning(
"MainFrame.evaluateUpdatePendingUpdateMenuItemCommand is deprecated. "
"Use SysTrayIcon.evaluateUpdatePendingUpdateMenuItemCommand instead.",
stack_info=True,
)
self.sysTrayIcon.evaluateUpdatePendingUpdateMenuItemCommand()
@blockAction.when(blockAction.Context.MODAL_DIALOG_OPEN)
def onExitCommand(self, evt):
if config.conf["general"]["askToExit"]:
self.prePopup()
d = ExitDialog(self)
d.Raise()
d.Show()
self.postPopup()
else:
if not core.triggerNVDAExit():
log.error("NVDA already in process of exiting, this indicates a logic error.")
def onNVDASettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog)
def onGeneralSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, GeneralSettingsPanel)
def onSelectSynthesizerCommand(self, evt):
self.popupSettingsDialog(SynthesizerSelectionDialog)
def onSpeechSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, SpeechSettingsPanel)
def onSelectBrailleDisplayCommand(self, evt):
self.popupSettingsDialog(BrailleDisplaySelectionDialog)
def onBrailleSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, BrailleSettingsPanel)
def onAudioSettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, AudioPanel)
def onPrivacyAndSecuritySettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, PrivacyAndSecuritySettingsPanel)
def onVisionSettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, VisionSettingsPanel)
def onKeyboardSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, KeyboardSettingsPanel)
def onMouseSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, MouseSettingsPanel)
def onTouchInteractionCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, TouchInteractionPanel)
def onReviewCursorCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, ReviewCursorPanel)
def onInputCompositionCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, InputCompositionPanel)
def onObjectPresentationCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, ObjectPresentationPanel)
def onBrowseModeCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, BrowseModePanel)
def onDocumentFormattingCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, DocumentFormattingPanel)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onAddonStoreSettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, AddonStorePanel)
def onUwpOcrCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, UwpOcrPanel)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onRemoteAccessSettingsCommand(self, evt):
self.popupSettingsDialog(NVDASettingsDialog, RemoteSettingsPanel)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onAdvancedSettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, AdvancedPanel)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onSpeechSymbolsCommand(self, evt):
self.popupSettingsDialog(SpeechSymbolsDialog)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onInputGesturesCommand(self, evt):
self.popupSettingsDialog(InputGesturesDialog)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onMagnifierSettingsCommand(self, evt: wx.CommandEvent):
self.popupSettingsDialog(NVDASettingsDialog, MagnifierPanel)
@staticmethod
def _copyVersionToClipboard(p: Payload):
versionStr = f"{versionInfo.version} ({versionInfo.version_detailed})"
api.copyToClip(versionStr)
# Translators: A message when the version number is copied to clipboard
# from the about dialog
ui.message(_("Copied to clipboard"))
def onAboutCommand(self, evt: wx.CommandEvent):
copyButton = Button(
id=wx.ID_COPY,
# Translators: The label for a button to copy the NVDA version number from the about dialog.
label=_("&Copy version number"),
callback=self._copyVersionToClipboard,
closesDialog=False,
)
# Translators: The title of the dialog to show about info for NVDA.
aboutDialog = MessageDialog(None, versionInfo.aboutMessage, _("About NVDA"))
aboutDialog.addButton(copyButton)
if globalVars.appArgs.secure:
button = next(c for c in aboutDialog.GetChildren() if c.GetId() == copyButton.id)
button.Disable()
aboutDialog.Show()
@blockAction.when(blockAction.Context.SECURE_MODE)
def onCheckForUpdateCommand(self, evt):
updateCheck.UpdateChecker().check()
def onViewLogCommand(self, evt):
logViewer.activate()
def onSpeechViewerEnabled(self, isEnabled):
# its possible for this to be called after the sysTrayIcon is destroyed if we are exiting NVDA
if self.sysTrayIcon and self.sysTrayIcon.menu_tools_toggleSpeechViewer:
self.sysTrayIcon.menu_tools_toggleSpeechViewer.Check(isEnabled)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onToggleSpeechViewerCommand(self, evt):
if not speechViewer.isActive:
speechViewer.activate()
else:
speechViewer.deactivate()
def onBrailleViewerChangedState(self, created):
# its possible for this to be called after the sysTrayIcon is destroyed if we are exiting NVDA
if self.sysTrayIcon and self.sysTrayIcon.menu_tools_toggleBrailleViewer:
self.sysTrayIcon.menu_tools_toggleBrailleViewer.Check(created)
@blockAction.when(blockAction.Context.SECURE_MODE)
def onToggleBrailleViewerCommand(self, evt):
import brailleViewer
if brailleViewer.isBrailleViewerActive():
brailleViewer.destroyBrailleViewer()
else:
brailleViewer.createBrailleViewerTool()
@blockAction.when(blockAction.Context.SECURE_MODE)
def onPythonConsoleCommand(self, evt):
import pythonConsole
if not pythonConsole.consoleUI:
pythonConsole.initialize()
pythonConsole.activate()
if NVDAState._allowDeprecatedAPI():
def onAddonsManagerCommand(self, evt: wx.MenuEvent):
log.warning(
"onAddonsManagerCommand is deprecated, use onAddonStoreCommand instead.",
stack_info=True,
)
self.onAddonStoreCommand(evt)
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.MODAL_DIALOG_OPEN,
blockAction.Context.WINDOWS_LOCKED,
blockAction.Context.WINDOWS_STORE_VERSION,
blockAction.Context.RUNNING_LAUNCHER,
)
def onAddonStoreCommand(self, evt: wx.MenuEvent):
from .addonStoreGui import AddonStoreDialog
from .addonStoreGui.viewModels.store import AddonStoreVM
_storeVM = AddonStoreVM()
_storeVM.refresh()
self.popupSettingsDialog(AddonStoreDialog, _storeVM)
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.MODAL_DIALOG_OPEN,
blockAction.Context.WINDOWS_LOCKED,
blockAction.Context.WINDOWS_STORE_VERSION,
blockAction.Context.RUNNING_LAUNCHER,
)
def onAddonStoreUpdatableCommand(self, evt: wx.MenuEvent | None):
from .addonStoreGui import AddonStoreDialog
from .addonStoreGui.viewModels.store import AddonStoreVM
from addonStore.models.status import _StatusFilterKey
_storeVM = AddonStoreVM()
_storeVM.refresh()
self.popupSettingsDialog(AddonStoreDialog, _storeVM, openToTab=_StatusFilterKey.UPDATE)
def onReloadPluginsCommand(self, evt):
import appModuleHandler
import globalPluginHandler
from NVDAObjects import NVDAObject
appModuleHandler.reloadAppModules()
globalPluginHandler.reloadGlobalPlugins()
NVDAObject.clearDynamicClassCache()
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.MODAL_DIALOG_OPEN,
)
def onCreatePortableCopyCommand(self, evt):
self.prePopup()
from . import installerGui
d = installerGui.PortableCreaterDialog(mainFrame)
d.Show()
self.postPopup()
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.MODAL_DIALOG_OPEN,
)
def onInstallCommand(self, evt):
from . import installerGui
installerGui.showInstallGui()
_CRFT_INTRO_MESSAGE: str = _(
# Translators: Explain the System Accessibility Repair Tool to users before running
"Welcome to the System Accessibility Repair Tool.\n\n"
"Installing and uninstalling programs, as well as other events, can damage accessibility entries in the "
"Windows registry. This can cause previously accessible elements to be presented incorrectly, "
'or can cause "unknown" or "pane" to be spoken or brailled in some applications or Windows components, '
"instead of the content you were expecting.\n\n"
"This tool attempts to fix such common problems. "
"Note that the tool must access the system registry, which requires administrative privileges.\n\n"
"Press Continue to run the tool now.",
)
"""
Contains the intro dialog contents for the System Accessibility Repair Tool.
Used by `gui.MainFrame.onRunCOMRegistrationFixesCommand`.
"""
@blockAction.when(
blockAction.Context.SECURE_MODE,
blockAction.Context.MODAL_DIALOG_OPEN,
)
def onRunCOMRegistrationFixesCommand(self, evt: wx.CommandEvent) -> None:
"""Manages the interactive running of the System Accessibility Repair Tool.
Shows a dialog to the user, giving an overview of what is going to happen.
If the user chooses to continue: runs the tool, and displays a completion dialog.
Cancels the run attempt if the user fails or declines the UAC prompt.
"""
# Translators: The title of various dialogs displayed when using the System Accessibility Repair Tool
genericTitle: str = _("System Accessibility Repair")
introDialog = _ContinueCancelDialog(
self,
genericTitle,
self._CRFT_INTRO_MESSAGE,
helpId="RunCOMRegistrationFixingTool",
)
response: int = introDialog.ShowModal()
if response != wx.OK:
log.debug("Run of System Accessibility Repair Tool canceled before UAC.")
return
progressDialog = IndeterminateProgressDialog(
mainFrame,
genericTitle,
# Translators: The message displayed while NVDA is running the System Accessibility Repair Tool
_("Please wait while NVDA attempts to repair your system's accessibility registrations..."),
)
error: str | None = None
try:
systemUtils.execElevated(config.SLAVE_FILENAME, ["fixCOMRegistrations"])
except WindowsError as e:
# 1223 is "The operation was canceled by the user."
if e.winerror == 1223:
# Same as if the user selected "no" in the initial dialog.
log.debug("Run of System Accessibility Repair Tool canceled during UAC.")
return
else:
log.error("Could not execute fixCOMRegistrations command", exc_info=True)
error = e # Hold for later display to the user
return # Safe because of finally block
except Exception:
log.error("Could not execute fixCOMRegistrations command", exc_info=True)
return # Safe because of finally block
finally: # Clean up the progress dialog, and display any important error to the user before returning
progressDialog.done()
del progressDialog
self.postPopup()
# If there was a Windows error, inform the user because it may have support value
if error is not None:
messageBox(
_(
# Translators: message shown to the user on System accessibility repair fail
"The System Accessibility Repair Tool was unsuccessful. This Windows "
"error may provide more information.\n{error}",
).format(error=error),
# Translators: The title of a System Accessibility Repair Tool dialog, when the tool has failed
_("System Accessibility Repair Failed"),
wx.OK,
)
# Display success dialog if there were no errors
messageBox(
_(
# Translators: Message shown when the System Accessibility Repair Tool completes.
"The System Accessibility Repair Tool has completed successfully.\n"
"It is highly recommended that you restart your computer now, to make sure the changes take full effect.",
),
genericTitle,
wx.OK,
)
@blockAction.when(blockAction.Context.MODAL_DIALOG_OPEN)
def onConfigProfilesCommand(self, evt):
self.prePopup()
from .configProfiles import ProfilesDialog
ProfilesDialog(mainFrame).Show()
self.postPopup()
class SysTrayIcon(wx.adv.TaskBarIcon):
def __init__(self, frame: MainFrame):
super(SysTrayIcon, self).__init__()
icon = wx.Icon(ICON_PATH, wx.BITMAP_TYPE_ICO)
self.SetIcon(icon, buildVersion.name)
self.menu = wx.Menu()
menu_preferences = self.preferencesMenu = wx.Menu()
item = menu_preferences.Append(
wx.ID_ANY,
# Translators: The label for the menu item to open NVDA Settings dialog.
_("&Settings..."),
# Translators: The description for the menu item to open NVDA Settings dialog.
_("NVDA settings"),
)
self.Bind(wx.EVT_MENU, frame.onNVDASettingsCommand, item)
if not globalVars.appArgs.secure:
# Translators: The label for a submenu under NvDA Preferences menu to select speech dictionaries.
menu_preferences.AppendSubMenu(self._createSpeechDictsSubMenu(frame), _("Speech &dictionaries"))
# Translators: The label for the menu item to open Punctuation/symbol pronunciation dialog.
item = menu_preferences.Append(wx.ID_ANY, _("&Punctuation/symbol pronunciation..."))
self.Bind(wx.EVT_MENU, frame.onSpeechSymbolsCommand, item)
# Translators: The label for the menu item to open the Input Gestures dialog.
item = menu_preferences.Append(wx.ID_ANY, _("I&nput gestures..."))
self.Bind(wx.EVT_MENU, frame.onInputGesturesCommand, item)
# Translators: The label for Preferences submenu in NVDA menu.
self.menu.AppendSubMenu(menu_preferences, _("&Preferences"))
menu_tools = self.toolsMenu = wx.Menu()
if not globalVars.appArgs.secure:
# Translators: The label for the menu item to open NVDA Log Viewer.
item = menu_tools.Append(wx.ID_ANY, _("View &log"))
self.Bind(wx.EVT_MENU, frame.onViewLogCommand, item)
item = self.menu_tools_toggleSpeechViewer = menu_tools.AppendCheckItem(
wx.ID_ANY,
# Translators: The label for the menu item to toggle Speech Viewer.
_("&Speech viewer"),
)
item.Check(speechViewer.isActive)
self.Bind(wx.EVT_MENU, frame.onToggleSpeechViewerCommand, item)
self.menu_tools_toggleBrailleViewer: wx.MenuItem = menu_tools.AppendCheckItem(
wx.ID_ANY,
# Translators: The label for the menu item to toggle Braille Viewer.
_("&Braille viewer"),
)
item = self.menu_tools_toggleBrailleViewer
self.Bind(wx.EVT_MENU, frame.onToggleBrailleViewerCommand, item)
import brailleViewer
self.menu_tools_toggleBrailleViewer.Check(brailleViewer.isBrailleViewerActive())
brailleViewer.postBrailleViewerToolToggledAction.register(frame.onBrailleViewerChangedState)
if not config.isAppX and NVDAState.shouldWriteToDisk():
# Translators: The label of a menu item to open the Add-on store
item = menu_tools.Append(wx.ID_ANY, _("&Add-on store..."))
self.Bind(wx.EVT_MENU, frame.onAddonStoreCommand, item)
if not globalVars.appArgs.secure and not config.isAppX:
# Translators: The label for the menu item to open NVDA Python Console.
item = menu_tools.Append(wx.ID_ANY, _("&Python console"))
self.Bind(wx.EVT_MENU, frame.onPythonConsoleCommand, item)
if not globalVars.appArgs.secure and not config.isAppX and not NVDAState.isRunningAsSource():
# Translators: The label for the menu item to create a portable copy of NVDA from an installed or another portable version.
item = menu_tools.Append(wx.ID_ANY, _("&Create portable copy..."))
self.Bind(wx.EVT_MENU, frame.onCreatePortableCopyCommand, item)
if not config.isInstalledCopy():
# Translators: The label for the menu item to install NVDA on the computer.
item = menu_tools.Append(wx.ID_ANY, _("&Install NVDA..."))
self.Bind(wx.EVT_MENU, frame.onInstallCommand, item)
# Translators: The label for the menu item to run the System Accessibility Repair Tool
item = menu_tools.Append(wx.ID_ANY, _("Run System Accessibility Repair Tool..."))
self.Bind(wx.EVT_MENU, frame.onRunCOMRegistrationFixesCommand, item)
if not config.isAppX:
# Translators: The label for the menu item to reload plugins.
item = menu_tools.Append(wx.ID_ANY, _("Reload plugins"))
self.Bind(wx.EVT_MENU, frame.onReloadPluginsCommand, item)
# Translators: The label for the Tools submenu in NVDA menu.
self.menu.AppendSubMenu(menu_tools, _("&Tools"))
self._appendHelpSubMenu(frame)
self._appendConfigManagementSection(frame)
if not globalVars.appArgs.secure:
self.menu.AppendSeparator()
# Translators: The label for the menu item to open donate page.
item = self.menu.Append(wx.ID_ANY, _("&Donate"))
self.Bind(wx.EVT_MENU, lambda evt: os.startfile(DONATE_URL), item)
self._appendPendingUpdateSection(frame)
self.menu.AppendSeparator()
item = self.menu.Append(
wx.ID_EXIT,
# Translators: The label for the menu item to exit NVDA
_("E&xit"),
# Translators: The help string for the menu item to exit NVDA
_("Exit NVDA"),
)
self.Bind(wx.EVT_MENU, frame.onExitCommand, item)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.onActivate)
self.Bind(wx.adv.EVT_TASKBAR_RIGHT_DOWN, self.onActivate)
def evaluateUpdatePendingUpdateMenuItemCommand(self):
try:
self.menu.Remove(self.installPendingUpdateMenuItem)
except Exception:
log.debug("Error while removing pending update menu item", exc_info=True)
if not globalVars.appArgs.secure and updateCheck and updateCheck.isPendingUpdate():
self.menu.Insert(self.installPendingUpdateMenuItemPos, self.installPendingUpdateMenuItem)
def onActivate(self, evt):
self.evaluateUpdatePendingUpdateMenuItemCommand()
mainFrame.prePopup()
import appModules.nvda
if not appModules.nvda.nvdaMenuIaIdentity:
# The NVDA app module doesn't know how to identify the NVDA menu yet.
# Signal that the NVDA menu has just been opened.
appModules.nvda.nvdaMenuIaIdentity = True
self.PopupMenu(self.menu)
if appModules.nvda.nvdaMenuIaIdentity is True:
# The NVDA menu didn't actually appear for some reason.
appModules.nvda.nvdaMenuIaIdentity = None
mainFrame.postPopup()
def _createSpeechDictsSubMenu(self, frame: MainFrame) -> wx.Menu:
subMenu_speechDicts = wx.Menu()
item = subMenu_speechDicts.Append(
wx.ID_ANY,
# Translators: The label for the menu item to open Default speech dictionary dialog.
_("&Default dictionary..."),
# Translators: The help text for the menu item to open Default speech dictionary dialog.
_("A dialog where you can set default dictionary by adding dictionary entries to the list"),
)
self.Bind(wx.EVT_MENU, frame.onDefaultDictionaryCommand, item)
item = subMenu_speechDicts.Append(
wx.ID_ANY,
# Translators: The label for the menu item to open Voice specific speech dictionary dialog.
_("&Voice dictionary..."),
_(
# Translators: The help text for the menu item
# to open Voice specific speech dictionary dialog.
"A dialog where you can set voice-specific dictionary by adding"
" dictionary entries to the list",
),
)
self.Bind(wx.EVT_MENU, frame.onVoiceDictionaryCommand, item)
item = subMenu_speechDicts.Append(
wx.ID_ANY,
# Translators: The label for the menu item to open Temporary speech dictionary dialog.
_("&Temporary dictionary..."),
# Translators: The help text for the menu item to open Temporary speech dictionary dialog.
_("A dialog where you can set temporary dictionary by adding dictionary entries to the edit box"),
)
self.Bind(wx.EVT_MENU, frame.onTemporaryDictionaryCommand, item)
return subMenu_speechDicts
def _appendConfigManagementSection(self, frame: MainFrame) -> None:
self.menu.AppendSeparator()
# Translators: The label for the menu item to open the Configuration Profiles dialog.
item = self.menu.Append(wx.ID_ANY, _("&Configuration profiles..."))
self.Bind(wx.EVT_MENU, frame.onConfigProfilesCommand, item)
item = self.menu.Append(
wx.ID_ANY,
# Translators: The label for the menu item to revert to saved configuration.
_("&Revert to saved configuration"),
# Translators: The help text for the menu item to revert to saved configuration.
_("Reset all settings to saved state"),
)
self.Bind(wx.EVT_MENU, frame.onRevertToSavedConfigurationCommand, item)
item = self.menu.Append(
wx.ID_ANY,
# Translators: The label for the menu item to reset settings to default settings.
# Here, default settings means settings that were there when the user first used NVDA.
_("Reset configuration to &factory defaults"),
# Translators: The help text for the menu item to reset settings to default settings.
# Here, default settings means settings that were there when the user first used NVDA.
_("Reset all settings to default state"),
)
self.Bind(wx.EVT_MENU, frame._confirmRevertToDefaultConfiguration, item)
if NVDAState.shouldWriteToDisk():
item = self.menu.Append(
wx.ID_SAVE,
# Translators: The label for the menu item to save current settings.
_("&Save configuration"),
# Translators: The help text for the menu item to save current settings.
_("Write the current configuration to nvda.ini"),
)
self.Bind(wx.EVT_MENU, frame.onSaveConfigurationCommand, item)
def _appendHelpSubMenu(self, frame: MainFrame) -> None:
self.helpMenu = wx.Menu()
if not globalVars.appArgs.secure:
# Translators: The label of a menu item to open NVDA user guide.
item = self.helpMenu.Append(wx.ID_ANY, _("&User Guide"))
self.Bind(wx.EVT_MENU, lambda evt: self._openDocumentationFile("userGuide.html"), item)
# Translators: The label of a menu item to open the Commands Quick Reference document.
item = self.helpMenu.Append(wx.ID_ANY, _("Commands &Quick Reference"))
self.Bind(wx.EVT_MENU, lambda evt: self._openDocumentationFile("keyCommands.html"), item)
# Translators: The label for the menu item to open What's New document.
item = self.helpMenu.Append(wx.ID_ANY, _("What's &new"))
self.Bind(wx.EVT_MENU, lambda evt: self._openDocumentationFile("changes.html"), item)
self.helpMenu.AppendSeparator()
# Translators: The label for the menu item to view the NVDA website
item = self.helpMenu.Append(wx.ID_ANY, _("NV Access &web site"))
self.Bind(wx.EVT_MENU, lambda evt: os.startfile(buildVersion.url), item)
# Translators: The label for the menu item to view the NVDA website's get help section
item = self.helpMenu.Append(wx.ID_ANY, _("&Help, training and support"))
self.Bind(wx.EVT_MENU, lambda evt: os.startfile(f"{buildVersion.url}/get-help/"), item)
# Translators: The label for the menu item to view the NVDA website's get help section
item = self.helpMenu.Append(wx.ID_ANY, _("NV Access &shop"))
self.Bind(wx.EVT_MENU, lambda evt: os.startfile(f"{buildVersion.url}/shop/"), item)
self.helpMenu.AppendSeparator()
# Translators: The label for the menu item to view the NVDA License.
item = self.helpMenu.Append(wx.ID_ANY, _("L&icense"))
self.Bind(wx.EVT_MENU, lambda evt: displayLicense(), item)
# Translators: The label for the menu item to open NVDA Welcome Dialog.
item = self.helpMenu.Append(wx.ID_ANY, _("We&lcome dialog..."))
self.Bind(wx.EVT_MENU, lambda evt: WelcomeDialog.run(), item)
if updateCheck:
# Translators: The label of a menu item to manually check for an updated version of NVDA.
item = self.helpMenu.Append(wx.ID_ANY, _("&Check for update..."))
self.Bind(wx.EVT_MENU, frame.onCheckForUpdateCommand, item)
# Translators: The label for the menu item to open About dialog to get information about NVDA.
item = self.helpMenu.Append(wx.ID_ABOUT, _("&About..."), _("About NVDA"))
self.Bind(wx.EVT_MENU, frame.onAboutCommand, item)
# Translators: The label for the Help submenu in NVDA menu.
self.menu.AppendSubMenu(self.helpMenu, _("&Help"))
def _openDocumentationFile(self, fileName: str) -> None:
helpFile = getDocFilePath(fileName)
if helpFile is None:
reportNoDocumentation(fileName, useMsgBox=True)
return
os.startfile(helpFile)
def _appendPendingUpdateSection(self, frame: MainFrame) -> None:
if not globalVars.appArgs.secure and updateCheck:
# installPendingUpdateMenuItemPos is later toggled based on if an update is available.
self.installPendingUpdateMenuItemPos = self.menu.GetMenuItemCount()
item = self.installPendingUpdateMenuItem = self.menu.Append(
wx.ID_ANY,
# Translators: The label for the menu item to run a pending update.
_("Install pending &update"),
# Translators: The description for the menu item to run a pending update.
_("Execute a previously downloaded NVDA update"),
)
self.Bind(wx.EVT_MENU, frame.onExecuteUpdateCommand, item)
def initialize():
global mainFrame
if mainFrame:
raise RuntimeError("GUI already initialized")
mainFrame = MainFrame()
wxLang = core.getWxLangOrNone()
if wxLang:
# otherwise the system default will be used
mainFrame.SetLayoutDirection(wxLang.LayoutDirection)
wx.GetApp().SetTopWindow(mainFrame)
import monkeyPatches
monkeyPatches.applyWxMonkeyPatches(mainFrame, winUser, wx)
# Set up GUI persistence
persistenceManager = wx.lib.agw.persist.PersistenceManager.Get()
persistenceManager.SetPersistenceFile(NVDAState.WritePaths.guiStateFile)
if not NVDAState.shouldWriteToDisk():
persistenceManager.DisableSaving()
def terminate():
global mainFrame
wx.lib.agw.persist.PersistenceManager.Free()
mainFrame = None
def showGui():
wx.CallAfter(mainFrame.showGui)
def runScriptModalDialog(dialog: wx.Dialog, callback: Callable[[int], Any] | None = None):
"""Run a modal dialog from a script.
This will not block the caller, but will instead call callback (if provided) with the result from the dialog.
The dialog will be destroyed once the callback has returned.
This function is deprecated.
Use :class:`message.MessageDialog` instead.
:param dialog: The dialog to show.
:param callback: The optional callable to call with the result from the dialog.
"""
warnings.warn(
"showScriptModalDialog is deprecated. Use an instance of message.MessageDialog and wx.CallAfter instead.",
DeprecationWarning,
)
def run():
res = displayDialogAsModal(dialog)
if callback:
callback(res)
dialog.Destroy()
wx.CallAfter(run)
class IndeterminateProgressDialog(wx.ProgressDialog):
def __init__(self, parent: wx.Window, title: str, message: str):
super().__init__(title, message, parent=parent)
self._speechCounter = -1
self.timer = wx.PyTimer(self.Pulse)
self.timer.Start(1000)
self.CentreOnScreen()
self.Raise()
def Pulse(self):
super(IndeterminateProgressDialog, self).Pulse()
# We want progress to be spoken on the first pulse and every 10 pulses thereafter.
# Therefore, cycle from 0 to 9 inclusive.
self._speechCounter = (self._speechCounter + 1) % 10
pbConf = config.conf["presentation"]["progressBarUpdates"]
if pbConf["progressBarOutputMode"] == "off":
return
if not pbConf["reportBackgroundProgressBars"] and not self.IsActive():
return
if pbConf["progressBarOutputMode"] in ("beep", "both"):
tones.beep(440, 40)
if pbConf["progressBarOutputMode"] in ("speak", "both") and self._speechCounter == 0:
# Translators: Announced periodically to indicate progress for an indeterminate progress bar.
speech.speakMessage(_("Please wait"))
def IsActive(self):
# 4714: In wxPython 3, ProgressDialog.IsActive always seems to return False.
return winUser.isDescendantWindow(winUser.getForegroundWindow(), self.Handle)