This guide provides information concerning NVDA development, including translation and the development of components for NVDA.
NVDA and its components are primarily written in the Python programming language. It is not the goal of this guide to teach you Python, though examples are provided through out this guide which will help to familiarise you with the Python syntax. Documentation and other resources related to the Python language can be found at www.python.org/
In order to support multiple languages/locales, NVDA must be translated and data specific to the locale must be provided. This section only includes information on custom NVDA file formats required for translation. Other items need to be translated, such as the NVDA user interface and documentation, but these use standard file formats. For complete documentation about translating NVDA, please see https://github.com/nvaccess/nvda/wiki/Translating
Sometimes, it can be very difficult or even impossible to distinguish one character from another. For example, two characters might be pronounced the same way, even though they are actually different characters. To help users when this occurs, character descriptions can be provided which describe the character in a unique way.
Character descriptions can be provided for a locale in a file named characterDescriptions.dic in the directory for the locale. This is a UTF-8 encoded text file. Blank lines and lines beginning with a "#" character are ignored. All other lines should contain a character, followed by a tab, then one or more descriptions separated by tabs.
# This is a comment. a alpha b bravo
See the file locale\en\characterDescriptions.dic for a full example.
In most cases, the characters in this file should be a single lower case character. It is assumed that characters will have the same description regardless of their case, so upper case characters are converted to lower case before looking up their character descriptions.
It is often useful to hear punctuation and other symbols pronounced as words when reading text, particularly when moving by character. Unfortunately, the pronunciation of symbols is inconsistent between speech synthesisers and many synthesisers do not speak many symbols and/or do not allow control over what symbols are spoken. Therefore, NVDA allows information about symbol pronunciation to be provided.
This is done for a locale by providing a file named symbols.dic in the directory for the locale. This is a UTF-8 encoded text file. Blank lines and lines beginning with a "#" character are ignored. All locales implicitly inherit the symbol information for English, though any of this information can be overridden.
The file contains two sections.
The first section is optional and defines regular expression patterns for complex symbols. Complex symbols are symbols which aren't simply a character or sequence of characters, but instead require a more complicated match. An example is the full stop (.) sentence ending in English. The "." is used for multiple purposes, so a more complicated check is required to determine whether this refers to the end of a sentence.
The complex symbols section begins with the line:
Subsequent lines contain a textual identifier used to identify the symbol, a tab and the regular expression pattern for that symbol. For example:
. sentence ending (?<=[^\s.])\.(?=[\"')\s]|$)
Again, the English symbols are inherited by all other locales, so you need not include any complex symbols already defined for English.
The second section provides information about when and how to pronounce all symbols. It begins with the line:
Subsequent lines should contain several fields separated by tabs. The only mandatory fields are the identifier and replacement. The default will be used for omitted fields. The fields are as follows:
Finally, a display name for the symbol can be provided in a comment after a tab at the end of the line. This will be shown to users when editing the symbol information and is especially useful for translators to define translated names for English complex symbols.
Here are some examples:
( left paren most
This means that the "(" character should be spoken as "left paren" only when the symbol level is set to most or higher; i.e. most or all.
, comma all always
This means that the "," character should be spoken as "comma" when the symbol level is set to all and that the character itself should always be preserved so that the synthesiser will pause appropriately.
. sentence ending point # . fin de phrase
This line appears in the French symbols.dic file. It means that the ". sentence ending" complex symbol should be spoken as "point". Level and preserve are not specified, so they will be taken from English. A display name is provided so that French users will know what the symbol represents.
Please see the file locale\en\symbols.dic for the English definitions which are inherited for all locales. This is also a good full example.
Plugins allow you to customize the way NVDA behaves overall or within a particular application. They are able to:
This section provides an introduction to developing plugins. Developers should consult the code documentation for a complete reference.
There are two types of plugins. These are:
If you wish to improve NVDA's access to a particular application, it is most likely you will want to write an App Module. In contrast, if you wish to add some overall functionality to NVDA (e.g. a script that announces current Wireless network strength while in any application), then a Global Plugin is what you want.
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.
The following few sections will talk separately about App Modules and Global Plugins. After this point, discussion is again more general.
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 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.
NVDA loads an App Module for an application as soon as it notices the application is running. The App Module is unloaded once the application is closed or when NVDA is exiting.
The following example App Module makes NVDA beep each time the focus changes within the notepad application. This example shows you the basic layout of an App Module.
Copy and paste the code between (but not including) the start and end markers into a new text file called notepad.py, which should be saved in the AppModules subdirectory. Be very careful to keep all tabs and spaces intact.
Once saved in the correct location, either restart NVDA or choose Reload Plugins found under Tools in the NVDA menu.
Finally, open Notepad and move the focus around the application; e.g. move along the menu bar, open some dialog boxes, etc. You should hear beeps each time the focus changes. Note though that if you move outside of Notepad - for instance, to Windows Explorer - you do not hear beeps.
--- start --- # Notepad App Module for NVDA # Developer guide example 1 import appModuleHandler class AppModule(appModuleHandler.AppModule): def event_gainFocus(self, obj, nextHandler): import tones tones.beep(550, 50) nextHandler() --- end ---
This App Module file starts with two comment lines, which describe what the file is for.
It then imports the appModuleHandler module, so that the App Module then has access to the base AppModule class.
Next, it defines a class called AppModule, which is inherited from appModuleHandler.AppModule.
Inside this class, it defines 1 or more events, scripts or gesture bindings. In this example, it defines one event method for gainFocus events (event_gainFocus), which plays a short beep each time it is executed. The implementation of this event is not important for the purposes of this example. The most important part is the class itself. Events will be covered in greater detail later.
As with other examples in this guide, remember to delete the created app module when you are finished testing and then restart NVDA or reload plugins, so that original functionality is restored.
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 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. This will all be covered in depth later.
NVDA loads all global plugins as soon as it starts, and unloads them on exit.
The following example Global Plugin Allows you to press NVDA+shift+v while anywhere in the Operating System to find out NVDA's version. This example is only to show you the basic layout of a Global Plugin.
Copy and paste the code between (but not including) the start and end markers into a new text file with a name of example2.py, which should be saved in the globalPlugins subdirectory. Be very careful to keep all tabs and spaces intact.
Once saved in the right place, either restart NVDA or choose Reload Plugins found under Tools in the NVDA menu.
From anywhere, you can now press NVDA+shift+v to have NVDA's version spoken and brailled.
--- start --- # Version announcement plugin for NVDA # Developer guide example 2 import globalPluginHandler from scriptHandler import script import ui import versionInfo class GlobalPlugin(globalPluginHandler.GlobalPlugin): @scriptHandler.script(gesture="kb:NVDA+shift+v") def script_announceNVDAVersion(self, gesture): ui.message(versionInfo.version) --- end ---
This Global Plugin file starts with two comment lines, which describe what the file is for.
It then imports the globalPluginHandler module, so that the Global Plugin has access to the base GlobalPlugin class.
It also imports a few other modules, namely ui, versionInfo and scriptHandler, which this specific plugin needs in order for it to perform the necessary actions to announce the version.
Next, it defines a class called GlobalPlugin, which is inherited from globalPluginHandler.GlobalPlugin.
Inside this class, it defines 1 or more events, scripts or gesture bindings. In this example, it defines a script method that performs the version announcement. The script decorator from the scriptHandler module is used to assign the NVDA+shift+v shortcut to this script. However, the details of the script and its binding are not important for the purposes of this example. The most important part is the class itself. More information about scripts and the script decorator can be found in the Defining script properties section of this guide.
As with other examples in this guide, remember to delete the created Global Plugin when finished testing and then restart NVDA or reload plugins, so that original functionality is restored.
NVDA represents controls and other GUI elements as NVDA Objects. These NVDA Objects contain standardised properties, such as name, role, value, states and description, which allow other parts of NVDA to query or present information about a control in a generalised way. For example, the OK button in a dialog would be represented as an NVDA Object with a name of "OK" and a role of button. Similarly, a checkbox with a label of "I agree" would have a name of "I agree", a role of checkbox, and if currently checked, a state of checked.
As there are many different GUI Toolkits and platform and accessibility APIs, NVDA Objects abstract these differences into a standard form that NVDA can use, regardless of the toolkit or API a particular control is made with. For example, the Ok button just discussed could be a widget in a Java application, an MSAA object, an IAccessible2 object or a UI Automation element.
NVDA Objects have many properties. Some of the most useful are:
There are also a few simplified navigation properties such as simpleParent, simpleNext, simpleFirstChild and simpleLastChild. These are like their respective navigation properties described above, but NVDA filters out unuseful objects. These properties are used when NVDA's simple review mode is turned on, which is the default. These simple properties may be easier to use, but the real navigation properties more closely reflect the underlying Operating System structure. Also, these may change in future versions of NVDA as improvements are made to simple review, so they should generally be avoided when programmatically locating specific objects.
When developing plugins, most of the time, it is not important what toolkit or API backs an NVDA Object, as the plugin will usually only access standard properties such as name, role and value. However, as plugins become more advanced, it is certainly possible to delve deeper into NVDA Objects to find out toolkit or API specific information if required.
Plugins make use of NVDA Objects in three particular ways:
Just like App Modules and Global Plugins, NVDA Objects can also define events, scripts and gesture bindings.
App Modules, Global Plugins and NVDA Objects can define special methods which can be bound to a particular piece of input such as a key press. NVDA refers to these methods as scripts.
A script is a standard Python instance method with a name starting with "script_"; e.g. "script_sayDateTime".
A script method takes two arguments:
As well as the actual script method, some form of gesture binding must be defined, so that NVDA knows what input should execute the script.
A gesture identifier string is a simple string representation of a piece of input. It consists of a two letter character code denoting the source of the input, an optional device in brackets, a colon (:) and one or more names separated by a plus (+) denoting the actual keys or input values.
Some examples of gesture string identifiers are:
Currently, the input sources in NVDA are:
When NVDA receives input, it looks for a matching gesture binding in a particular order. Once a gesture binding is found, the script is executed and no further bindings are used, nore is that particular gesture passed on automatically to the Operating System.
The order for gesture binding lookup is:
For NVDA 2018.3 and above, the recommended way to set script properties is by means of the so called script decorator. In short, a decorator is a function that modifies the behavior of a particular function. The script decorator modifies the script in such a way that it will be properly bound to the desired gestures. Furthermore, it ensures that the script is listed with the description you specify, and that it is categorised under the desired category in the input gestures dialog.
In order for you to use the script decorator, you will have to import it from the scriptHandler module.
from scriptHandler import script
After that, just above your script definition, add the script decorator, providing it the desired arguments. For example:
--- start --- @script( description=_("Speaks the date and time"), category=inputCore.SCRCAT_MISC, gestures=["kb:NVDA+shift+t", "kb:NVDA+alt+r"] ) def script_sayDateTime(self, gesture): --- end ---
In this example, your script will be listed in the input gestures dialog under the "Miscellaneous" category. It will have the description "Speaks the date and time", and will be bound to the "NVDA+shift+t" and "NVDA+alt+r" key combinations on the keyboard.
The following keyword arguments can be used when applying the script decorator:
Though the script decorator makes the script definition process a lot easier, there are more ways of binding gestures and setting script properties. For example, a special "__gestures" Python dictionary can be defined as a class variable on an App Module, Global Plugin or NVDA Object. This dictionary should contain gesture identifier strings pointing to the name of the requested script, without the "script_" prefix. You can also specify a description of the script in the function's docstring. Furthermore, an alternative way of specifying the script's category is by means of setting a "category" attribute on the script function to a string containing the name of the category.
The following Global Plugin allows you to press NVDA+leftArrow to have the window class of the current focus announced, and NVDA+rightArrow to have the window control ID of the current focus announced. This example shows you how to define one or more scripts and gesture bindings on a class such as an App Module, Global Plugin or NVDA Object.
Copy and paste the code between (but not including) the start and end markers into a new text file with a name of example3.py, which should be saved in the globalPlugins subdirectory. Be very careful to keep all tabs and spaces intact.
Once saved in the right place, either restart NVDA or choose Reload Plugins found under Tools in the NVDA menu.
--- start --- #Window utility scripts for NVDA #Developer guide example 3 import globalPluginHandler from scriptHandler import script import ui import api class GlobalPlugin(globalPluginHandler.GlobalPlugin): @script( description=_("Announces the window class name of the current focus object"), gesture="kb:NVDA+leftArrow" ) def script_announceWindowClassName(self, gesture): focusObj = api.getFocusObject() name = focusObj.name windowClassName = focusObj.windowClassName ui.message("class for %s window: %s" % (name, windowClassName)) @script( description=_("Announces the window control ID of the current focus object"), gesture="kb:NVDA+rightArrow" ) def script_announceWindowControlID(self, gesture): focusObj = api.getFocusObject() name = focusObj.name windowControlID = focusObj.windowControlID ui.message("Control ID for %s window: %d" % (name, windowControlID)) --- end ---
When NVDA detects particular toolkit, API or Operating System events, it abstracts these and fires its own internal events on plugins and NVDA Objects.
Although most events are related to a specific NVDA Object (e.g. name change, gain focus, state change, etc.), these events can be handled at various levels. When an event is handled, it is stopped from going further down the chain. However, code inside the event can choose to propagate it further if needed.
The order of levels through which the event passes until an event method is found is:
Events are Python instance methods, with a name starting with "event_" followed by the actual name of the event (e.g. gainFocus).
These event methods take slightly different arguments depending at what level they are defined.
If an event for an NVDA Object is defined on an NVDA Object itself, the method only takes one mandatory argument which is the 'self' argument; i.e. the NVDA Object instance). Some events may take extra arguments, though this is quite rare.
If an event for an NVDA Object is defined on a Global Plugin, App Module or Tree Interceptor, the event takes the following arguments:
Some common NVDA Object events are:
There are many other events, though those listed above are usually the most useful.
For an example of an event handled by an App Module, please refer to example 1 (focus beeps in notepad).
App Modules have one very useful property called "sleepMode", which if set to true almost completely disables NVDA within that application. Sleep Mode is very useful for self voicing applications that have their own screen reading functionality, or perhaps even some games that need full use of the keyboard.
Although sleep mode can be toggled on and off by the user with the key command NVDA+shift+s, a developer can choose to have sleep mode enabled by default for an application. This is done by providing an App Module for that application which simply sets sleepMode to True in the AppModule class.
The following code can be copied and pasted in to a text file, then saved in the appModules directory with the name of the application you wish to enable sleep mode for. As always, the file must have a .py extension.
--- start --- import appModuleHandler class AppModule(appModuleHandler.AppModule): sleepMode = True --- end ---
Providing custom NVDA Object classes is probably the most powerful and useful way to improve the experience of an application in an NVDA plugin. This method allows you to place all the needed logic for a particular control altogether in one NVDA Object class for that control, rather than scattering code for many controls across a plugin's events.
There are two steps to providing a custom NVDA Object class:
When defining a custom NVDAObject class, you have many NVDAObject base classes to choose from. These base classes contain the base support for the particular accessibility or OS API underlying the control, such as win32, MSAA or Java access Bridge. You should usually inherit your custom NVDAObject class from the highest base class you need in order to choose your class in the first place. For example, if you choose to use your custom NVDAObject class when the window class name is "Edit" and the window control ID is 15, you should probably inherit from NVDAObjects.window.Window, as clearly you are aware that this is a Window object. Similarly, if you match on MSAA's accRole property, you would probably need to inherit from NVDAObjects.IAccessible.IAccessible. You should also consider what properties you are going to override on the custom NVDA Object. For instance, if you are going to override an IAccessible specific property, such as shouldAllowIAccessibleFocusEvent, then you need to inherit from NVDAObjects.IAccessible.IAccessible.
the chooseNVDAObjectOverlayClasses method can be implemented on app modules or global plugin classes. It takes 3 arguments:
Inside this method, you should decide which custom NVDA Object class(es) (if any) this NVDA Object should use by checking its properties, etc. If a custom class should be used, it must be inserted into the class list, usually at the beginning. You can also remove classes chosen by NVDA from the class list, although this is rarely required.
This app module for notepad provides a command to report the number of characters in edit fields. You can activate it using NVDA+l. Notice that the command is specific to edit fields; i.e. it only works while you are focused in an edit field, rather than anywhere in the application.
The following code can be copied and pasted in to a text file, then saved in the appModules directory with the name of notepad.py.
--- start --- import appModuleHandler from scriptHandler import script from NVDAObjects.IAccessible import IAccessible import controlTypes import ui class AppModule(appModuleHandler.AppModule): def chooseNVDAObjectOverlayClasses(self, obj, clsList): if obj.windowClassName == "Edit" and obj.role == controlTypes.ROLE_EDITABLETEXT: clsList.insert(0, EnhancedEditField) class EnhancedEditField(IAccessible): @script(gesture="kb:NVDA+l") def script_reportLength(self, gesture): ui.message("%d" % len(self.value)) --- end ---
Sometimes, you may wish to make only small changes to an NVDA Object in an application, such as overriding its name or role. In these cases, you don't need the full power of a custom NVDA Object class. To do this, you can use the NVDAObject_init event available only on App Modules.
The event_NVDAObject_init method takes two arguments:
Inside this method, you can check whether this object is relevant and then override properties accordingly.
This app module for notepad makes NVDA report Notepad's main edit field as having a name of "content". That is, when it receives focus, NVDA will say "Content edit".
The following code can be copied and pasted in to a text file, then saved in the appModules directory with the name of notepad.py.
--- start --- import appModuleHandler from NVDAObjects.window import Window class AppModule(appModuleHandler.AppModule): def event_NVDAObject_init(self, obj): if isinstance(obj, Window) and obj.windowClassName == "Edit" and obj.windowControlID == 15: obj.name = "Content" --- end ---
To make it easy for users to share and install plugins and drivers, they can be packaged in to a single NVDA add-on package which the user can then install into a copy of NVDA via the Add-ons Manager found under Tools in the NVDA menu. Add-on packages are only supported in NVDA 2012.2 and later. An add-on package is simply a standard zip archive with the file extension of nvda-addon which contains a manifest file, optional install/uninstall code and one or more directories containing plugins and/or drivers.
If your add-on includes files which contain non-ASCII (non-English) characters, you should create the zip archive such that it uses UTF-8 file names. This means that these files can be extracted properly on all systems, regardless of the system's configured language. Unfortunately, many zip archivers do not support this, including Windows Explorer. Generally, it has to be explicitly enabled even in archivers that do support it. http://www.7-zip.org/ supports this, though it must be enabled by specifying the "cu=on" method parameter.
Each add-on package must contain a manifest file named manifest.ini. This must be a UTF-8 encoded text file. This manifest file contains key = value pares declaring info such as the add-on's name, version and description.
Although it is highly suggested that manifests contain all fields, the fields marked as mandatory must be included. Otherwise, the add-on will not install.
--- start --- name = MyTestAddon summary = Cool Test Add-on version = 1.0 description = An example add-on showing how to create add-ons! author = Michael Curran <email@example.com> url = http://www.nvda-project.org/wiki/Development --- end ---
The following plugins and drivers can be included in an add-on:
If you need to execute code as your add-on is being installed or uninstalled from NVDA (e.g. to validate license information or to copy files to a custom location), you can provide a Python file called installTasks.py in the archive which contains special functions that NVDA will call while installing or uninstalling your add-on. This file should avoid loading any modules that are not absolutely necessary, especially Python C extensions or dlls from your own add-on, as this could cause later removal of the add-on to fail. However, if this does happen, the add-on directory will be renamed and then deleted after the next restart of NVDA. Finally, it should not depend on the existence or state of other add-ons, as they may not be installed, have already been removed or not yet be initialized.
NVDA will look for and execute an onInstall function in installTasks.py after it has finished extracting the add-on into NVDA. Note that although the add-on will have been extracted at this time, its directory will have a .pendingInstall suffix until NVDA is restarted, the directory is renamed and the add-on is really loaded for the first time. If this function raises an exception, the installation of the add-on will fail and its directory will be cleaned up.
NVDA will look for and execute an onUninstall function in installTasks.py when NVDA is restarted after the user has chosen to remove the add-on. After this function completes, the add-on's directory will automatically be removed. As this happens on NVDA startup before other components are initialized, this function cannot request input from the user.
It is possible to provide locale-specific information and messages for your add-on. Locale information can be stored in a locale directory in the archive. This directory should contain directories for each language it supports, using the same language code format as the rest of NVDA; e.g. en for English, fr_CA for French Canadian.
Each of these language directories can contain a locale-specific manifest file called manifest.ini, which can contain a small subset of the manifest fields for translation. These fields are summary and description. All other fields will be ignored.
Each language directory can also contain gettext information, which is the system used to translate the rest of NVDA's user interface and reported messages. As with the rest of NVDA, an nvda.mo compiled gettext database file should be placed in the LC_MESSAGES directory within this directory. to allow plugins in your add-on to access gettext message information via calls to _(), you must initialize translations at the top of each Python module by calling addonHandler.initTranslation(). For more information about gettext and NVDA translation in general, please read http://www.nvda-project.org/wiki/TranslatingNVDA
Documentation for an add-on should be placed in a doc directory in the archive. Similar to the locale directory, this directory should contain directories for each language in which documentation is available.
Users can access documentation for a particular add-on by opening the Add-ons Manager, selecting the add-on and pressing the Add-on help button. This will open the file named in the docFileName parameter of the manifest. NVDA will search for this file in the appropriate language directories. For example, if docFileName is set to readme.html and the user is using English, NVDA will open doc\en\readme.html.
The NVDA Python console emulates the interactive Python interpreter from within NVDA. It is a development tool which is useful for debugging, general inspection of NVDA internals or inspection of the accessibility hierarchy of an application.
The console can be activated in two ways:
The console is similar to the standard interactive Python interpreter. Input is accepted one line at a time. The current line is processed when enter is pressed. You can navigate through the history of previously entered lines using the up and down arrow keys.
Output (responses from the interpreter) will be spoken when enter is pressed. The f6 key toggles between the input and output controls.
Closing the console window simply hides it. This allows the user to return to the session as it was left when it was closed, including history and variables.
For convenience, the following modules and variables are automatically imported in the console: sys, os, wx, log (from logHandler), api, queueHandler, speech, braille
Whenever NVDA+control+z is pressed, certain variables available in the console will be assigned according to the current state of NVDA. These variables are:
A remote Python console is available for situations where remote debugging of NVDA is useful. It is similar to the local Python console discussed above, but is accessed via TCP.
Please be aware that this is a huge security risk. You should only enable it if you are connected to trusted networks.
To enable the remote Python console, use the local Python console to import remotePythonConsole and call remotePythonConsole.initialize(). You can then connect to it via TCP port 6832.
History of previously entered lines is not supported.
The namespace is the same as the namespace in the local Python console.
There are some special functions: