Overview
App Modules allow you to improve NVDA’s support for specific applications. When an application is running, NVDA automatically loads the corresponding app module and unloads it when the application closes.
App modules are perfect for adding application-specific functionality, customizing how NVDA presents information from an application, or fixing accessibility issues in specific programs.
Basic App Module Structure
App modules must:
Be placed in the appModules directory
Be named after the application’s executable (e.g., notepad.py for notepad.exe)
Define a class called AppModule that inherits from appModuleHandler.AppModule
Basic App Module
appModuleHandler.py
# notepad.py
# App Module for Notepad
import appModuleHandler
from scriptHandler import script
import ui
import tones
class AppModule ( appModuleHandler . AppModule ):
def event_gainFocus ( self , obj , nextHandler ):
"""Called when an object gains focus."""
tones.beep( 550 , 50 )
nextHandler()
@script (
description = "Announces the application version" ,
gesture = "kb:NVDA+shift+v"
)
def script_announceVersion ( self , gesture ):
"""Announce the application version."""
ui.message( f "Application: { self .appName } " )
Naming App Modules
Standard Naming
For most applications, name your app module file after the executable:
notepad.exe → notepad.py
explorer.exe → explorer.py
firefox.exe → firefox.py
File names containing dots (other than .py) are converted to underscores. For example, my.app.exe would use my_app_exe.py.
Custom Mapping
For executables with names that conflict with Python naming rules or when you want to map multiple executables to one app module, use a global plugin to register the mapping:
import appModuleHandler
import globalPluginHandler
class GlobalPlugin ( globalPluginHandler . GlobalPlugin ):
def __init__ ( self , * args , ** kwargs ):
super (). __init__ ( * args, ** kwargs)
# Map "time.exe" to "time_app_mod" module
appModuleHandler.registerExecutableWithAppModule( "time" , "time_app_mod" )
def terminate ( self , * args , ** kwargs ):
super ().terminate( * args, ** kwargs)
appModuleHandler.unregisterExecutable( "time" )
Handling Events
App modules can respond to events that occur in their application. Events are methods named event_eventName.
Focus Events
Object Events
Application Events
import appModuleHandler
import ui
class AppModule ( appModuleHandler . AppModule ):
def event_gainFocus ( self , obj , nextHandler ):
"""Called when an object gains focus."""
ui.message( f "Focus on { obj.name } " )
nextHandler() # Always call nextHandler to propagate the event
def event_focusEntered ( self , obj , nextHandler ):
"""Called when focus moves inside a container."""
if obj.role == controlTypes.Role. DIALOG :
ui.message( f "Entered dialog: { obj.name } " )
nextHandler()
Common Events
Object gained keyboard focus
Object lost keyboard focus
Focus moved inside a container object (the object is an ancestor of the focus)
Object became the new foreground/active top-level object
Caret (insertion point) moved within the object
Creating Scripts
Scripts are commands that users can execute via keyboard shortcuts. Use the @script decorator to define scripts:
import appModuleHandler
from scriptHandler import script
import ui
import api
class AppModule ( appModuleHandler . AppModule ):
@script (
description = "Announces the current object's class name" ,
category = "My Application" ,
gesture = "kb:NVDA+shift+c"
)
def script_announceClassName ( self , gesture ):
"""Announce the window class name."""
focusObj = api.getFocusObject()
ui.message( f "Class: { focusObj.windowClassName } " )
@script (
description = "Announces window control ID" ,
gestures = [ "kb:NVDA+shift+i" , "kb:NVDA+alt+i" ]
)
def script_announceControlID ( self , gesture ):
"""Announce the control ID."""
focusObj = api.getFocusObject()
ui.message( f "Control ID: { focusObj.windowControlID } " )
Script Decorator Parameters
User-visible description shown in Input Gestures dialog
Category to group similar scripts (defaults to “Miscellaneous”)
Single gesture string (e.g., "kb:NVDA+shift+v")
Whether script applies to focus ancestor objects
Run script even when input help is active
Allow script to run when sleep mode is active
App Module Properties
Essential Properties
class AppModule ( appModuleHandler . AppModule ):
# The process ID (read-only, set in __init__)
processID: int
# The application name (read-only, set in __init__)
appName: str
# Handle to the process
processHandle: int
# Whether NVDA should sleep in this application
sleepMode: bool = False
# Product name from executable version info
productName: str
# Product version from executable version info
productVersion: str
# Whether the process is 64-bit
is64BitProcess: bool
# Whether this is a Windows Store app
isWindowsStoreApp: bool
Sleep Mode
Sleep mode disables NVDA in an application, useful for self-voicing applications:
import appModuleHandler
class AppModule ( appModuleHandler . AppModule ):
"""App module for a self-voicing application."""
sleepMode = True
Users can toggle sleep mode with NVDA+Shift+S.
Customizing NVDA Objects
App modules can customize how NVDA Objects behave for specific controls:
import appModuleHandler
from NVDAObjects.IAccessible import IAccessible
import controlTypes
class AppModule ( appModuleHandler . AppModule ):
def chooseNVDAObjectOverlayClasses ( self , obj , clsList ):
"""Choose overlay classes for objects in this application."""
if obj.windowClassName == "CustomButton" :
# Insert custom class at the beginning
clsList.insert( 0 , CustomButton)
class CustomButton ( IAccessible ):
"""Custom handling for custom buttons."""
def _get_name ( self ):
"""Provide better name for custom buttons."""
name = super ().name
return f "Custom: { name } "
def _get_role ( self ):
"""Override role."""
return controlTypes.Role. BUTTON
Hosted Apps
Some executables host multiple apps (e.g., javaw.exe, wwahost.exe). For these, you need to:
Name the app module after the hosted app’s internal name
For wwahost.exe, inherit from the wwahost app module
Java App
WWAHost App
WebView2 App
# test.py - for Java app named "test" running in javaw.exe
import appModuleHandler
class AppModule ( appModuleHandler . AppModule ):
pass
UIA Handling
Control whether to use UI Automation for specific windows:
import appModuleHandler
class AppModule ( appModuleHandler . AppModule ):
def isGoodUIAWindow ( self , hwnd ):
"""Return True if UIA should be used for this window."""
# Check window class name and force UIA
return False
def isBadUIAWindow ( self , hwnd ):
"""Return True if UIA should be avoided for this window."""
# Detect broken UIA implementations
return False
def shouldProcessUIAPropertyChangedEvent ( self , sender , propertyId ):
"""Control whether to process UIA property changes."""
# Filter out noisy events
return True
Practical Example
Here’s a complete app module for a custom application:
# myapp.py
# App Module for My Application
import appModuleHandler
from scriptHandler import script
import api
import ui
import controlTypes
from NVDAObjects.IAccessible import IAccessible
class AppModule ( appModuleHandler . AppModule ):
def __init__ ( self , * args , ** kwargs ):
super (). __init__ ( * args, ** kwargs)
self .customData = {}
def event_appModule_gainFocus ( self ):
"""Application gained focus."""
ui.message( "My Application" )
def event_gainFocus ( self , obj , nextHandler ):
"""Handle focus events."""
if obj.role == controlTypes.Role. LISTITEM :
# Custom handling for list items
self ._announceListItem(obj)
nextHandler()
def _announceListItem ( self , obj ):
"""Custom announcement for list items."""
position = obj.positionInfo
if position:
ui.message(
f " { obj.name } , item { position[ 'indexInGroup' ] } of { position[ 'similarItemsInGroup' ] } "
)
@script (
description = "Shows custom information" ,
category = "My Application" ,
gesture = "kb:NVDA+shift+i"
)
def script_showInfo ( self , gesture ):
"""Display custom information dialog."""
focusObj = api.getFocusObject()
ui.message( f "Object: { focusObj.name } , Role: { focusObj.role } " )
def chooseNVDAObjectOverlayClasses ( self , obj , clsList ):
"""Customize NVDA objects."""
if obj.windowClassName == "MyCustomControl" :
clsList.insert( 0 , MyCustomControl)
class MyCustomControl ( IAccessible ):
"""Custom NVDA object for MyCustomControl."""
def _get_name ( self ):
"""Provide better name."""
# Custom logic to get name
return super ().name or "Unnamed custom control"
def _get_description ( self ):
"""Provide helpful description."""
return "This is a custom control"
Testing
Enable scratchpad
Go to NVDA Settings > Advanced and enable the scratchpad directory.
Create app module
Place your app module in %APPDATA%\nvda\scratchpad\appModules\.
Launch application
Start the target application.
Reload plugins
Press NVDA+Control+F3 to reload without restarting NVDA.
Test functionality
Test your events, scripts, and customizations.
Best Practices
Always call nextHandler() in event methods to propagate events
Don’t perform long-running operations in events
Use the @script decorator instead of __gestures dictionary
Provide clear descriptions for all scripts
Test with different NVDA configurations
Use ui.message() for user feedback
Use log.debug(), log.info(), log.warning() for debugging
Check obj.role and obj.windowClassName for object identification
Cache expensive computations in instance variables