Permalink
Browse files

Provide configurable developer scratchpad dir rather than automatic l…

…oading of custom code (#9238)

* NVDA no longer automatically loads custom code from package directories in the NVDA user configuration directory. Rather it will load tem from subdirectories in a new 'scratchpad' directory in the NVDA user configuration directory, but only if the open in the advanced category is enabled.

* System tests use Developer scratchpad directory for the Robot globalPlugin.

* Don't load from the developer scratchpad dir if NVDA is running in secure mode.

* Update more system test paths to scratchpad.

* Remove spaces at start of line.

* Update what's new.
  • Loading branch information...
michaelDCurran committed Feb 11, 2019
1 parent 960983e commit d1a8a710d37f5a376ebf9f32600d7cb857554b38
@@ -167,16 +167,23 @@ Both App Modules and Global Plugins share a common look and feel.
They are both Python source files (with a .py extension), they both define a special class containing all events, scripts and bindings, and they both may define custom classes to access controls, text content and complex documents.
However, they do differ in some ways.

Custom appModules and globalPlugins can be packaged into NVDA add-ons.
This allows easy distribution, and provides a safe way for the user to install and uninstall the custom code.
Please refer to the Add-ons section later on in this document.

In order to test the code while developing, you can place it in a special 'scratchpad' directory in your NVDA user configuration directory.
You will also need to configure NVDA to enable loading of custom code from the Developer Scratchpad Directory, by enabling this in the Advanced category of NVDA's Settings dialog.
The Advanced category also contains a button to easily open the Developer Scratchpad directory if enabled.

The following few sections will talk separately about App Modules and Global Plugins.
After this point, discussion is again more general.

++ Basics of an App Module ++
App Module files have a .py extension, and are named the same as the main executable of the application for which you wish them to be used.
For example, an App Module for notepad would be called notepad.py, as notepad's main executable is called notepad.exe.

App Module files must be placed in the appModules subdirectory of the user's NVDA user configuration directory.
For more information on where to find the user configuration directory, please see the NVDA user guide.

App Module files must be placed in the appModules subdirectory of an add-on, or of the scratchpad directory of the NVDA user configuration directory.

App Modules must define a class called AppModule, which inherits from appModuleHandler.AppModule.
This class can then define event and script methods, gesture bindings and other code.
This will all be covered in depth later.
@@ -231,8 +238,7 @@ As with other examples in this guide, remember to delete the created app module
++ Basics of a Global Plugin ++
Global Plugin files have a .py extension, and should have a short unique name which identifies what they do.

Global Plugin files must be placed in the globalPlugins subdirectory of the user's NVDA user configuration directory.
For more information on where to find the user configuration directory, please see the NVDA user guide.
Global plugin files must be placed in the globalPlugins subdirectory of an add-on, or of the scratchpad directory of the NVDA user configuration directory.

Global Plugins must define a class called GlobalPlugin, which inherits from globalPluginHandler.GlobalPlugin.
This class can then define event and script methods, gesture bindings and other code.
@@ -137,6 +137,18 @@ def getSystemConfigPath():
pass
return None

def getScratchpadDir(ensureExists=False):
""" Returns the path where custom appModules, globalPlugins and drivers can be placed while being developed."""
path=os.path.join(globalVars.appArgs.configPath,'scratchpad')
if ensureExists:
if not os.path.isdir(path):
os.makedirs(path)
for subdir in ('appModules','brailleDisplayDrivers','globalPlugins','synthDrivers'):
subpath=os.path.join(path,subdir)
if not os.path.isdir(subpath):
os.makedirs(subpath)
return path

def initConfigPath(configPath=None):
"""
Creates the current configuration path if it doesn't exist. Also makes sure that various sub directories also exist.
@@ -149,7 +161,7 @@ def initConfigPath(configPath=None):
os.makedirs(configPath)
subdirs=["speechDicts","profiles"]
if not isAppX:
subdirs.extend(["addons", "appModules","brailleDisplayDrivers","synthDrivers","globalPlugins"])
subdirs.append("addons")
for subdir in subdirs:
subdir=os.path.join(configPath,subdir)
if not os.path.isdir(subdir):
@@ -295,6 +307,7 @@ def getConfigDirs(subpath=None):
@return: The configuration directories in the order in which they should be searched.
@rtype: list of str
"""
log.warning("getConfigDirs is deprecated. Use globalVars.appArgs.configPath instead")
return [os.path.join(dir, subpath) if subpath else dir
for dir in (globalVars.appArgs.configPath,)
]
@@ -309,16 +322,22 @@ def addConfigDirsToPythonPackagePath(module, subdir=None):
"""
if isAppX or globalVars.appArgs.disableAddons:
return
if not subdir:
subdir = module.__name__
# Python 2.x doesn't properly handle unicode import paths, so convert them.
dirs = [dir.encode("mbcs") for dir in getConfigDirs(subdir)]
dirs.extend(module.__path__ )
module.__path__ = dirs
# FIXME: this should not be coupled to the config module....
import addonHandler
for addon in addonHandler.getRunningAddons():
addon.addToPackagePath(module)
if globalVars.appArgs.secure or not conf['development']['enableScratchpadDir']:
return
if not subdir:
subdir = module.__name__
fullPath=os.path.join(getScratchpadDir(),subdir)
# Python 2.x doesn't properly handle unicode import paths, so convert them.
fullPath=fullPath.encode("mbcs")
# Insert this path at the beginning of the module's search paths.
# The module's search paths may not be a mutable list, so replace it with a new one
pathList=[fullPath]
pathList.extend(module.__path__)
module.__path__=pathList

class ConfigManager(object):
"""Manages and provides access to configuration.
@@ -214,6 +214,9 @@
[editableText]
caretMoveTimeoutMs = integer(min=0, max=2000, default=100)
[development]
enableScratchpadDir = boolean(default=false)
""").format(latestSchemaVersion=latestSchemaVersion)

#: The configuration specification
@@ -206,6 +206,8 @@ def main():
log.debug("loading config")
import config
config.initialize()
if config.conf['development']['enableScratchpadDir']:
log.info("Developer Scratchpad mode enabled")
if not globalVars.appArgs.minimal and config.conf["general"]["playStartAndExitSounds"]:
try:
nvwave.playWaveFile("waves\\start.wav")
@@ -1939,6 +1939,25 @@ def makeSettings(self, settingsSizer):
panelText =_("Warning! The following settings are for advanced users. Changing them may cause NVDA to function incorrectly. Please only change these if you know what you are doing or have been specifically instructed by NVDA developers.")
sHelper.addItem(wx.StaticText(self, label=panelText))

# Translators: This is the label for a group of Advanced options in the
# Advanced settings panel
groupText = _("NVDA Development")
devGroup = guiHelper.BoxSizerHelper(self, sizer=wx.StaticBoxSizer(wx.StaticBox(self, label=groupText), wx.VERTICAL))
sHelper.addItem(devGroup)

# Translators: This is the label for a checkbox in the
# Advanced settings panel.
label = _("Enable loading custom code from Developer Scratchpad directory")
self.scratchpadCheckBox=devGroup.addItem(wx.CheckBox(self, label=label))
self.scratchpadCheckBox.SetValue(config.conf["development"]["enableScratchpadDir"])
self.scratchpadCheckBox.Bind(wx.EVT_CHECKBOX,self.onToggleScratchpadCheckBox)

# Translators: the label for a button in the Advanced settings category
label=_("Open developer scratchpad directory")
self.openScratchpadButton=devGroup.addItem(wx.Button(self, label=label))
self.openScratchpadButton.Bind(wx.EVT_BUTTON,self.onOpenScratchpadDir)
self.openScratchpadButton.Enable(config.conf["development"]["enableScratchpadDir"])

# Translators: This is the label for a group of Advanced options in the
# Advanced settings panel
groupText = _("Microsoft UI Automation")
@@ -1985,7 +2004,15 @@ def makeSettings(self, settingsSizer):
self.logCategoriesList.CheckedItems=[index for index,x in enumerate(self.logCategories) if config.conf['debugLog'][x]]
self.logCategoriesList.Select(0)

def onToggleScratchpadCheckBox(self,evt):
self.openScratchpadButton.Enable(evt.IsChecked())

def onOpenScratchpadDir(self,evt):
path=config.getScratchpadDir(ensureExists=True)
os.startfile(path)

def onSave(self):
config.conf["development"]["enableScratchpadDir"]=self.scratchpadCheckBox.IsChecked()
config.conf["UIA"]["useInMSWordWhenAvailable"]=self.UIAInMSWordCheckBox.IsChecked()
config.conf["editableText"]["caretMoveTimeoutMs"]=self.caretMoveTimeoutSpinControl.GetValue()
for index,key in enumerate(self.logCategories):
@@ -2545,7 +2572,8 @@ class NVDASettingsDialog(MultiCategorySettingsDialog):
if winVersion.isUwpOcrAvailable():
categoryClasses.append(UwpOcrPanel)
# And finally the Advanced panel which should always be last.
categoryClasses.append(AdvancedPanel)
if not globalVars.appArgs.secure:
categoryClasses.append(AdvancedPanel)

def makeSettings(self, settingsSizer):
# Ensure that after the settings dialog is created the name is set correctly
@@ -45,7 +45,7 @@
nvdaProfileWorkingDir = _pJoin(tempDir, "nvdaProfile")
nvdaLogFilePath = _pJoin(nvdaProfileWorkingDir, 'nvda.log')
systemTestSpyAddonName = "systemTestSpy"
testSpyPackageDest = _pJoin(nvdaProfileWorkingDir, "globalPlugins")
testSpyPackageDest = _pJoin(nvdaProfileWorkingDir, "scratchpad", "globalPlugins")
outDir = builtIn.get_variable_value("${OUTPUT DIR}")
testOutputNvdaLogsDir = _pJoin(outDir, "nvdaTestRunLogs")

@@ -122,7 +122,7 @@ def setup_nvda_profile(self, settingsFileName):
# install the test spy speech synth
opSys.copy_file(
_pJoin(systemTestSourceDir, "libraries", "speechSpy.py"),
_pJoin(nvdaProfileWorkingDir, "synthDrivers", "speechSpy.py")
_pJoin(nvdaProfileWorkingDir, "scratchpad", "synthDrivers", "speechSpy.py")
)

def teardown_nvda_profile(self):
@@ -135,7 +135,7 @@ def teardown_nvda_profile(self):
recursive=True
)
opSys.remove_file(
_pJoin(nvdaProfileWorkingDir, "synthDrivers", "speechSpy.py")
_pJoin(nvdaProfileWorkingDir, "scratchpad", "synthDrivers", "speechSpy.py")
)

def _startNVDAProcess(self):
@@ -8,3 +8,5 @@ schemaVersion = 2
allowUsageStats = False
[speech]
synth = speechSpy
[development]
enableScratchpadDir = True
@@ -8,3 +8,5 @@ schemaVersion = 2
allowUsageStats = False
[speech]
synth = speechSpy
[development]
enableScratchpadDir = True
@@ -31,8 +31,10 @@ What's New in NVDA
- When NVDA is started with the `--portable-path` command line parameter, the provided path is automatically filled in when trying to create a portable copy of NVDA using the NVDA menu. (#8623)
- Updated the path to the Norwegian braille table to reflect the standard from the year 2015. (#9170)
- When navigating by paragraph (control+arrows) or navigating by table cell (control+alt+arrows), the existence of spelling errors will no longer be announced, even if NVDA is configured to announce these automatically. This is because paragraphs and table cells can be quite large, and calculating spelling errors in some applications can be very costly. (#9217)


- NVDA no longer automatically loads custom appModules, globalPlugins and braille and synth drivers from the NVDA user configuration directory. This code should be instead packaged as an add-on with correct version information, ensuring that incompatible code is not run with current versions of NVDA. (#9238)
- For developers who need to test code as it is being developed, enable NVDA's developer scratchpad directory in the Advanced category of NVDA settings, and place your code in the 'scratchpad' directory found in the NVDA user configuration directory when this option is enabled.


== Bug Fixes ==
- OneCore speech synthesizer: On Windows 10 April 2018 and above, large chunks of silence are no longer inserted between speech utterances. (#8985)
- When moving by character in plain text controls (such as Notepad) or browse mode, 32 bit emoji characters consisting of two UTF-16 code points (such as 🤦) will now read properly. (#8782)
@@ -1607,6 +1607,17 @@ This combo box allows you to choose the language to be used for text recognition
Warning! The settings in this category are for advanced users and may cause NVDA to not function correctly if configured in the wrong way.
Only make changes to these settings if you are sure you know what you are doing or if you have been specifically instructed to by an NVDA developer.

==== Enable loading custom code from Developer Scratchpad Directory ====
When developing add-ons for NVDA, it is useful to be able to test code as you are writing it.
This option when enabled, allows NVDA to load custom appModules, globalPlugins, brailleDisplayDrivers and synthDrivers, from a special developer scratchpad directory in your NVDA user configuration directory.
Previously NVDA would load custom code directly from the user configuration directory, with no way of disabling this.
This option is off by default, ensuring that no untested code is ever run in NVDA with out the user's explicit knowledge.
If you wish to distribute custom code to others, you should package it as an NVDA add-on.

==== Open Developer Scratchpad Directory ====
This button opens the directory where you can place custom code while developing it.
This button is only enabled if NVDA is configured to enable loading custom code from the Developer Scratchpad Directory.

==== Use UI automation to access Microsoft Word document controls when available ====
When this option is enabled, NVDA will try to use the Microsoft UI Automation accessibility api in order to fetch information from Microsoft Word document controls.
This includes in Microsoft Word itself, and also the Microsoft Outlook message viewer and composer.
@@ -1876,10 +1887,6 @@ When using an older version of NVDA, some new add-ons may not be compatible eith
Attempting to install an incompatible add-on will result in an error explaining why the add-on is considered incompatible.
To inspect these incompatible add-ons, you can use the "view incompatible add-ons" button to launch the incompatible add-ons manager.

In the past, it was possible to extend NVDA's functionality by copying individual plugins and drivers into your NVDA user Configuration directory.
Although this version of NVDA may still load them, they will not be shown in the Add-on Manager.
It is best to remove these files from your configuration and install the appropriate add-on if one is available.

To access the Add-ons Manager from anywhere, please assign a custom gesture using the [Input Gestures dialog #InputGestures].

++ Incompatible Add-ons Manager ++[incompatibleAddonsManager]

0 comments on commit d1a8a71

Please sign in to comment.