# NVDA (NonVisual Desktop Access) ## Introduction NVDA (NonVisual Desktop Access) is a free, open-source screen reader for Microsoft Windows developed by NV Access in collaboration with a global community of contributors. It provides accessibility to the Windows operating system and applications through speech synthesis and braille output, enabling blind and visually impaired users to navigate computers independently. NVDA supports multiple accessibility APIs including MSAA/IAccessible2, UI Automation, and Java Access Bridge, making it compatible with a wide range of applications from standard Windows controls to complex web browsers and office suites. The project is built primarily in Python with performance-critical components in C++, following a modular, event-driven architecture. NVDA's core functionality includes object navigation, text review, speech and braille output, input gesture handling, and a comprehensive extension system. The extensibility framework allows developers to create add-ons, app modules, and global plugins to customize behavior for specific applications or add new features. With built-in support for 75+ application-specific modules and multiple speech synthesizers and braille displays, NVDA serves as both a production screen reader and a platform for accessibility research and development. ## APIs and Key Functions ### Get Focus Object Retrieves the currently focused UI element as an NVDAObject, which is the core abstraction for all accessible objects in NVDA's object hierarchy. ```python import api # Get the current focus object focusedObject = api.getFocusObject() # Access object properties print(f"Name: {focusedObject.name}") print(f"Role: {focusedObject.role}") print(f"Value: {focusedObject.value}") print(f"States: {focusedObject.states}") # Navigate the object tree parentObject = focusedObject.parent nextObject = focusedObject.next firstChild = focusedObject.firstChild # Get object location on screen location = focusedObject.location # Returns (x, y, width, height) # Create a text range for the object's content textInfo = focusedObject.makeTextInfo(textInfos.POSITION_ALL) text = textInfo.text ``` ### Set Focus Object Updates NVDA's internal tracking of the focused object, triggering appropriate focus events without changing the operating system's actual focus. ```python import api import eventHandler # Set a new focus object newFocusObject = someNVDAObject success = api.setFocusObject(newFocusObject) if success: # The focus has been updated successfully # This will trigger event_loseFocus on the old object # and event_gainFocus on the new object print("Focus updated") else: # Failed due to invalid object or security restrictions print("Focus update failed") # Get foreground window object foregroundObj = api.getForegroundObject() # Set foreground object (typically the top-level window) api.setForegroundObject(foregroundObj) ``` ### Queue and Execute Events NVDA's event system queues UI events for asynchronous processing through a chain of handlers including global plugins, app modules, and object-specific handlers. ```python import eventHandler # Queue an event for processing eventHandler.queueEvent("nameChange", obj) eventHandler.queueEvent("gainFocus", obj, extraParam="value") # Check if events are pending if eventHandler.isPendingEvents(): print("Events are queued") # Check for specific event type if eventHandler.isPendingEvents("gainFocus"): print("Focus events are pending") # Check for events on specific object if eventHandler.isPendingEvents("nameChange", obj): print("Name change pending for this object") # Events are automatically executed from the event queue # The execution chain is: # 1. Global plugins (event_eventName methods) # 2. App modules (event_eventName methods) # 3. Tree interceptors # 4. NVDAObject itself ``` ### Speech Output Controls text-to-speech output with support for speech sequences, commands, and multiple speech modes. ```python import speech from speech.commands import PitchCommand, RateCommand, LangChangeCommand # Simple speech speech.speak(["Hello world"]) # Complex speech sequence with commands sequence = [ "The temperature is ", PitchCommand(offset=50), # Increase pitch "25 degrees", PitchCommand(offset=0), # Reset pitch LangChangeCommand("es"), # Switch to Spanish "Hola", LangChangeCommand("en"), # Back to English ] speech.speak(sequence) # Cancel current speech speech.cancelSpeech() # Control speech mode from speech.speech import SpeechMode, setSpeechMode setSpeechMode(SpeechMode.off) # No speech setSpeechMode(SpeechMode.beeps) # Beeps instead of speech setSpeechMode(SpeechMode.talk) # Normal speech (default) setSpeechMode(SpeechMode.onDemand) # Only speak on explicit commands # Speak object information speech.speakObject(obj, reason=controlTypes.OutputReason.FOCUS) # Speak text with specific reason speech.speakText("Error occurred", reason=controlTypes.OutputReason.MESSAGE) ``` ### App Module Development Create application-specific customizations by extending the AppModule base class and implementing event handlers and scripts. ```python # File: appModules/myapp.py import appModuleHandler import api import speech import controlTypes from NVDAObjects import NVDAObject class AppModule(appModuleHandler.AppModule): """App module for myapp.exe""" def event_gainFocus(self, obj: NVDAObject, nextHandler): """Handle focus events for this application""" # Custom processing if obj.role == controlTypes.Role.BUTTON: speech.speak(["Custom announcement for button"]) # Call next handler in chain nextHandler() def event_nameChange(self, obj: NVDAObject, nextHandler): """Handle name change events""" if obj.name and "status" in obj.name.lower(): # Announce status changes immediately speech.cancelSpeech() speech.speak([obj.name]) nextHandler() def _get_statusBar(self): """Override status bar retrieval for this app""" # Custom logic to find status bar fg = api.getForegroundObject() for child in fg.children: if child.role == controlTypes.Role.STATUSBAR: return child raise NotImplementedError() def terminate(self): """Cleanup when app module is unloaded""" # Release resources super().terminate() ``` ### Global Plugin Development Implement system-wide functionality that works across all applications by creating global plugins. ```python # File: globalPlugins/myPlugin.py import globalPluginHandler import api import ui import speech import scriptHandler from scriptHandler import script import inputCore class GlobalPlugin(globalPluginHandler.GlobalPlugin): """System-wide plugin example""" def __init__(self): super().__init__() # Initialize plugin state self.lastAnnouncedTime = 0 def event_gainFocus(self, obj, nextHandler): """Receive all focus events system-wide""" # Custom focus handling if obj.role == controlTypes.Role.EDITABLETEXT: # Do something for all edit fields pass nextHandler() @script( gesture="kb:NVDA+shift+t", description="Announce current time", category=inputCore.SCRCAT_MISC ) def script_announceTime(self, gesture): """Script bound to NVDA+Shift+T""" import datetime now = datetime.datetime.now() timeStr = now.strftime("%I:%M %p") ui.message(timeStr) def chooseNVDAObjectOverlayClasses(self, obj, clsList): """Add custom overlay classes to objects""" if obj.windowClassName == "CustomControl": # Insert custom overlay class clsList.insert(0, MyCustomOverlayClass) def terminate(self): """Cleanup when plugin is terminated""" super().terminate() ``` ### Extension Points System Use extension points to create notification and filtering systems for loosely coupled components. ```python import extensionPoints # Create an Action extension point (notification) myAction = extensionPoints.Action() # Register handlers def onSomethingHappened(someArg=None): print(f"Action occurred with arg: {someArg}") myAction.register(onSomethingHappened) # Notify all handlers myAction.notify(someArg=42) # Create a Filter extension point (data transformation) messageFilter = extensionPoints.Filter[str]() def filterMessage(message: str, context=None) -> str: return message.upper() messageFilter.register(filterMessage) # Apply filter through all handlers result = messageFilter.apply("hello world", context="test") # result = "HELLO WORLD" # Create a Decider extension point (veto decisions) shouldProceed = extensionPoints.Decider() def checkCondition(param=None) -> bool: return param > 0 shouldProceed.register(checkCondition) # Make decision (False if any handler returns False) if shouldProceed.decide(param=5): print("All handlers agreed to proceed") # Create an AccumulatingDecider (collect all decisions) parameterHandler = extensionPoints.AccumulatingDecider(defaultDecision=False) def handleParam(param=None) -> bool: return param == "myParam" parameterHandler.register(handleParam) # Get accumulated decision wasHandled = parameterHandler.decide(param="myParam") ``` ### Configuration Management Access and modify NVDA's configuration settings with profile support and change notifications. ```python import config # Access configuration settings synthName = config.conf["speech"]["synth"] brailleDisplay = config.conf["braille"]["display"] # Modify settings config.conf["speech"]["rate"] = 50 config.conf["speech"]["volume"] = 100 # Save configuration to disk config.conf.save() # Register for configuration events def onConfigSave(): print("Configuration is being saved") config.pre_configSave.register(onConfigSave) config.post_configSave.register(onConfigSave) # Handle profile switches def onProfileSwitch(): print("Configuration profile switched") # Reload settings that depend on config config.post_configProfileSwitch.register(onProfileSwitch) # Handle configuration reset def onConfigReset(factoryDefaults: bool): if factoryDefaults: print("Factory defaults applied") else: print("Configuration reloaded from disk") config.pre_configReset.register(onConfigReset) config.post_configReset.register(onConfigReset) # Access nested settings if config.conf["presentation"]["reportTooltips"]: # Tooltips should be reported pass # Check feature flags from config.featureFlag import FeatureFlag if config.conf["featureFlag"]["flag_name"]: # Feature is enabled pass ``` ### NVDAObject Overlay Classes Create custom NVDAObject classes to override behavior for specific UI controls. ```python from NVDAObjects.IAccessible import IAccessible from NVDAObjects.UIA import UIA import controlTypes class MyCustomButton(IAccessible): """Custom overlay for specific button controls""" # Override role detection role = controlTypes.Role.BUTTON def _get_name(self): """Override name property""" # Custom name retrieval logic baseName = super().name return f"Custom: {baseName}" def _get_description(self): """Provide custom description""" return "This is a special button" def _get_states(self): """Override states""" states = super().states # Add or remove states if self.someCondition(): states.add(controlTypes.State.CHECKED) return states def event_gainFocus(self): """Handle focus on this object""" speech.speak([self.name, "special button"]) super().event_gainFocus() def script_activate(self, gesture): """Override activation behavior""" # Custom activation logic self.doClick() ui.message("Button activated") @classmethod def kwargsFromSuper(cls, kwargs, **other): """Filter overlay class application""" # Only apply this overlay if specific conditions met if kwargs.get("windowClassName") == "MyButtonClass": return kwargs return None # Register overlay in global plugin or app module def chooseNVDAObjectOverlayClasses(self, obj, clsList): if obj.windowClassName == "MyButtonClass": clsList.insert(0, MyCustomButton) ``` ### Input Gestures and Scripts Define keyboard commands and other input gestures that trigger script functions. ```python import ui from scriptHandler import script import inputCore class MyScriptableObject: """Example showing script binding patterns""" # Define gesture map at class level __gestures = { "kb:control+shift+a": "myAction", "kb:NVDA+b": "anotherAction", "br(freedomScientific):leftWizWheelUp": "scrollUp", } @script( description="Perform my custom action", category=inputCore.SCRCAT_MISC, gesture="kb:control+alt+m" ) def script_myAction(self, gesture): """Script triggered by control+shift+a or control+alt+m""" ui.message("My action executed") @script( description="Another action", category=inputCore.SCRCAT_BROWSEMODE ) def script_anotherAction(self, gesture): """Script triggered by NVDA+b""" # Get repeat count if scriptHandler.getLastScriptRepeatCount() == 0: ui.message("First press") elif scriptHandler.getLastScriptRepeatCount() == 1: ui.message("Double press") else: ui.message("Triple press") def script_scrollUp(self, gesture): """Braille display gesture handler""" ui.message("Scrolling up") # Script categories for organization inputCore.SCRCAT_BROWSEMODE # Browse mode navigation inputCore.SCRCAT_FOCUS # Focus navigation inputCore.SCRCAT_KBEMU # Keyboard emulation inputCore.SCRCAT_MISC # Miscellaneous inputCore.SCRCAT_CONFIG # Configuration inputCore.SCRCAT_SPEECH # Speech control inputCore.SCRCAT_SYSTEM # System control ``` ### TextInfo Object Usage Work with text ranges and text navigation using the TextInfo abstraction layer. ```python import textInfos import api # Get text info for focused object obj = api.getFocusObject() info = obj.makeTextInfo(textInfos.POSITION_FIRST) # Text info units textInfos.UNIT_CHARACTER textInfos.UNIT_WORD textInfos.UNIT_LINE textInfos.UNIT_SENTENCE textInfos.UNIT_PARAGRAPH textInfos.UNIT_PAGE textInfos.UNIT_STORY # Expand to unit boundaries info.expand(textInfos.UNIT_WORD) wordText = info.text # Move by units info.move(textInfos.UNIT_LINE, 1) # Move forward 1 line info.move(textInfos.UNIT_WORD, -3) # Move back 3 words # Get all text from object allTextInfo = obj.makeTextInfo(textInfos.POSITION_ALL) fullText = allTextInfo.text # Selection handling selInfo = obj.makeTextInfo(textInfos.POSITION_SELECTION) selectedText = selInfo.text # Compare endpoints info1 = obj.makeTextInfo(textInfos.POSITION_CARET) info2 = obj.makeTextInfo(textInfos.POSITION_FIRST) comparison = info1.compareEndPoints(info2, "startToStart") # Returns: -1 if info1 before info2, 0 if equal, 1 if after # Copy text info infoCopy = info.copy() # Set selection newInfo = obj.makeTextInfo(textInfos.POSITION_FIRST) newInfo.expand(textInfos.UNIT_WORD) newInfo.updateSelection() # Select the first word # Format information formatConfig = textInfos.FormatConfig(reportFontName=True, reportFontSize=True) formatInfo = info.getTextWithFields(formatConfig) ``` ### Tree Interceptor and Browse Mode Implement tree interceptors for complex documents like web pages that require special navigation modes. ```python import browseMode import treeInterceptorHandler from virtualBuffers import VirtualBuffer import controlTypes class MyBrowseModeDocument(browseMode.BrowseModeTreeInterceptor): """Custom browse mode implementation""" def __init__(self, rootNVDAObject): super().__init__(rootNVDAObject) # Initialize virtual buffer self._loadBuffer() def _get_isReady(self): """Check if document is ready for navigation""" return self._isReady def _loadBuffer(self): """Load document content into virtual buffer""" # Custom buffer loading logic self._isReady = True def event_gainFocus(self, obj, nextHandler): """Handle focus entering document""" if self.passThrough: # In pass-through mode, let native control handle nextHandler() else: # In browse mode, handle navigation specially self._handleBrowseModeFocus(obj) def script_togglePassThrough(self, gesture): """Toggle between browse and focus modes""" self.passThrough = not self.passThrough if self.passThrough: ui.message("Focus mode") else: ui.message("Browse mode") # Quick navigation items def _quickNavScript( self, gesture, itemType, direction, errorMessage, readUnit ): """Navigate to next/previous item of type""" # itemType examples: "heading", "link", "table" if direction == "next": item = self._findNextItem(itemType) else: item = self._findPreviousItem(itemType) if item: self._moveTo(item) else: ui.message(errorMessage) # Register tree interceptor for specific object def update(obj): """Create tree interceptor for object if needed""" if shouldHaveTreeInterceptor(obj): return MyBrowseModeDocument(obj) return None # Check if object has tree interceptor obj = api.getFocusObject() if obj.treeInterceptor: ti = obj.treeInterceptor if isinstance(ti, browseMode.BrowseModeTreeInterceptor): isPassThrough = ti.passThrough ``` ### Add-on Manifest and Packaging Create an add-on with proper manifest configuration and package structure. ```ini # File: addon/manifest.ini [DEFAULT] name = myAddon summary = "My NVDA Add-on" description = """A comprehensive add-on that adds custom functionality to NVDA. This add-on demonstrates various extension capabilities.""" author = "Your Name " url = https://github.com/yourname/myAddon version = 1.0.0 docFileName = readme.html minimumNVDAVersion = 2023.1.0 lastTestedNVDAVersion = 2024.1.0 updateChannel = stable ``` ``` # Add-on directory structure: myAddon/ ├── addon/ │ ├── manifest.ini │ ├── doc/ │ │ └── en/ │ │ └── readme.md │ ├── globalPlugins/ │ │ └── myGlobalPlugin.py │ ├── appModules/ │ │ └── myapp.py │ ├── synthDrivers/ │ │ └── mySynth.py │ ├── brailleDisplayDrivers/ │ │ └── myDisplay.py │ └── locale/ │ └── en/ │ └── LC_MESSAGES/ │ └── nvda.po ├── buildVars.py └── sconstruct ``` ```python # File: buildVars.py # Build variables for SCons build system import os # Add-on information variables addon_info = { "addon_name": "myAddon", "addon_version": "1.0.0", "addon_description": "My custom NVDA add-on", "addon_author": "Your Name ", "addon_url": "https://github.com/yourname/myAddon", "addon_docFileName": "readme.html", "addon_minimumNVDAVersion": "2023.1.0", "addon_lastTestedNVDAVersion": "2024.1.0", } # Python files to include pythonFiles = ["addon/globalPlugins/*.py", "addon/appModules/*.py"] # Files to include in the package i18nFiles = ["addon/locale/**/*.po"] ``` ## Summary NVDA serves as both a production screen reader and a comprehensive platform for accessibility development on Windows. Its primary use cases include enabling blind users to access standard Windows applications, web browsers, office suites, and development tools through speech and braille output. For developers, NVDA provides a rich extension ecosystem where add-ons can customize speech output, add support for new applications, implement custom navigation modes for complex documents, and integrate new hardware devices like braille displays or speech synthesizers. The event-driven architecture with its scriptable objects allows fine-grained control over user interaction, while the NVDAObject abstraction layer provides consistent access to UI elements across different accessibility APIs. Integration with NVDA typically follows several patterns: creating app modules for application-specific behavior, developing global plugins for system-wide features, implementing tree interceptors for complex document navigation, and building complete add-ons that bundle multiple components. The extension points system enables loose coupling between components, while the configuration system provides persistent storage with profile support. Developers can leverage NVDA's existing infrastructure for speech synthesis, braille output, input handling, and UI object navigation, focusing their efforts on the unique requirements of their accessibility solutions. The project's modular design and comprehensive Python API make it an ideal platform for accessibility research, custom accessibility tools, and specialized screen reader implementations.