Scripting or Plugins for the NVDA Screenreader

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.

Documentation

Development environment

Getting the source code

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:

  1. You can check out all the source code using git. See the website for documentation: essentially, install a Git client and run "git clone git://git.nvaccess.org/nvda.git" to get the code in the local folder.
  2. You can browse all the source code online at the NVDA Project code browser. Look for the folder called appModules.
  3. You can just download all the plugins/scripts as of April 2013 here: Download NVDA Plugins (Scripts) Code (April 2013) as a zip. Again, see the appModules folder for existing NVDA scripts. Of course, this will be outdated and wrong, so you're better getting the code from the NVDA Project website.

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.

Example NVDA scripts

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.

Getting started - Hello World – an NVDA script for Notepad

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.

Making NVDA shut up in a self-voicing application (doctts.py)

import appModuleHandler
class AppModule(appModuleHandler.AppModule):
		sleepMode=True

Making NVDA use another script (winmail.py)

#Use the msimn.py script
from msimn import *

Make NVDA shut up for this (self-voicing) application (doctts.py)

import appModuleHandler
class AppModule(appModuleHandler.AppModule):
	sleepMode=True

Write to the NVDA log

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")

Assigning key to a script function

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")

Handling different windows in the same application (winamp.py)

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.

Change the name of a control (audacity.py)

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('&','')

Use the Windows API (audacity.py)

See "Change the name of a control" (audacity.py) above. The winUser module lets you call the Windows window functions, like getWindowText.

Relabel buttons (sndrec32.py)

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

WebbIE home page