The NVDA screenreader is a free (GNU Public Licence) screenreader for Microsoft Windows. It can be scripted to work differently in different applications. This page has some notes and resources on scripting that I pulled together while writing a script for WebbIE - it is not an official site of the NVDA Project.
NVDA scripts are called App Modules, which are a type of Plugin.
Looking at the source code lets you see how other scripts work and how the functions that scripts call work.
You have three ways to get the source code:
"git clone git://git.nvaccess.org/nvda.git"
to get the code in the local folder.Each .py file in the appLocations folder is an application plugin, a script for a particular program. The name of the .py file indicates the program it is scripting, so explorer.py is the script for explorer.exe – Windows Explorer.
This is a brief set of notes from trying to create some NVDA scripts. You should probably start with the actual official documentation: Plugins. You'll find examples and explanations there.
These are all from the NVDA source code except for the Hello World example.
In a text editor, create a file called notepad.py. This will therefore be the script for notepad.exe - Windows Notepad.
Save it in %appdata%\nvda\appModules
. For Windows 7 that will look something like C:\Users\[your username]\AppData\Roaming\nvda\appModules
Paste this into the file:
# Notepad App Module for NVDA
# Scripting example "Hello World "
import appModuleHandler
class AppModule(appModuleHandler.AppModule):
def event_gainFocus(self, obj, nextHandler):
import speech
speech.speakMessage("Hello World ")
nextHandler()
Save the file. Start NVDA. Start Notepad. You should hear "Hello World" when Notepad becomes the foreground window. You can Alt+Tab away and back and you should hear the message every time.
import appModuleHandler
class AppModule(appModuleHandler.AppModule):
sleepMode=True
#Use the msimn.py script
from msimn import *
import appModuleHandler
class AppModule(appModuleHandler.AppModule):
sleepMode=True
Note that you have to change the default debug logging level in Preferences, General Settings to "debug warning" to get this output. You can also just Speak text and watch the Speech Viewer.
from logHandler import log
log.debugWarning("Hello world")
imports speech
class AppModule(appModuleHandler.AppModule):
# Script action on moving the cursor up/down or pressing pageup/dwn:
__helloWorldGestures = (
"kb:upArrow",
"kb:downArrow",
"kb:pageUp",
"kb:pageDown",
)
# Put the key assignment in iniOverlayClass
def initOverlayClass(self):
for gesture in self.__helloWorldGestures:
self.bindGesture(gesture, "helloWorld")
def script_helloWorld(self,gesture):
gesture.send() # Let the key go through to the application.
speech.speakmessage("Hello world")
For example, you may want different keystrokes to operate in the main Word document area, the spellcheck window and the Open file menu. This code lets you assign different functions and keys to different dialogues and windows in the same application.
class AppModule(appModuleHandler.AppModule):
#chooseNVDAObjectOverlayClasses gets called if you create it.
#Add the different handlers to clsList like this:
def chooseNVDAObjectOverlayClasses(self, obj, clsList):
windowClass = obj.windowClassName
if windowClass == "Winamp PE":
clsList.insert(0, winampPlaylistEditor)
elif windowClass == "Winamp v1.x":
clsList.insert(0, winampMainWindow)
class winampMainWindow(IAccessible):
#Code for the main window in WinAMP goes here – e.g. Play, Stop etc.
class winampPlaylistEditor(winampMainWindow):
#Code for the playlist editor in WinAMP goes here – e.g. delete, add etc.
Some UI controls (e.g. buttons) can have bad or missing names when NVDA gets to them. The following script identifies an offending button, gets its name from a Windows API call, and populates the NVDAObject name value with the new name.
import appModuleHandler
import winUser
import controlTypes
class AppModule(appModuleHandler.AppModule):
def event_NVDAObject_init(self,obj):
#Identify the offending control we want to rename
if obj.windowClassName=="Button" and not obj.role in [controlTypes.ROLE_MENUBAR, controlTypes.ROLE_MENUITEM, controlTypes.ROLE_POPUPMENU]:
#Change the value of obj.name to what the user should hear. obj.name=winUser.getWindowText(obj.windowHandle).replace('&','')
See "Change the name of a control" (audacity.py) above. The winUser module lets you call the Windows window functions, like getWindowText.
You may want to provide more useful labels for controls in an application than are provided natively, or even provide labels when none are given. For example, buttons on a form may not have names in NVDA. This example shows a script that labels buttons with meaningful names.
import appModuleHandler
import controlTypes
mainWindowButtonNames={
205:_("Rewind"),
206:_("Fast forward"),
207:_("Play"),
208:_("Stop"),
209:_("Record")
}
class AppModule(appModuleHandler.AppModule):
def event_NVDAObject_init(self, obj):
if obj.role == controlTypes.ROLE_BUTTON:
try:
obj.name=mainWindowButtonNames[obj.windowControlID]
except KeyError:
pass