Understanding NVDA’s input handling and command binding system
Scripts are methods that execute in response to user input (gestures). NVDA’s script system provides a powerful way to bind keyboard shortcuts, braille display buttons, and other input to custom functionality.
The modern way to define scripts uses the @script decorator from scriptHandler:
Script decorator syntax
from scriptHandler import scriptimport inputCoreclass MyPlugin(GlobalPlugin): @script( # Description shown in input gestures dialog description="Reports the current date and time", # Category for organization category=inputCore.SCRCAT_MISC, # Single gesture gesture="kb:NVDA+shift+t", # Or multiple gestures gestures=["kb:NVDA+shift+t", "kb:NVDA+alt+t"], # Allow propagation to focus ancestors canPropagate=False, # Run even in input help mode bypassInputHelp=False, # Run even in sleep mode allowInSleepMode=False, # Resume say all after execution resumeSayAllMode=None, # Speak when speech mode is "on-demand" speakOnDemand=False ) def script_sayDateTime(self, gesture): import datetime now = datetime.datetime.now() ui.message(f"{now.strftime('%I:%M %p, %A %B %d, %Y')}")
class GlobalPlugin(globalPluginHandler.GlobalPlugin): __gestures = { "kb:NVDA+shift+v": "announceVersion", "kb:NVDA+shift+t": "announceTime", } def script_announceVersion(self, gesture): """Announce NVDA version""" # Used as description import buildVersion ui.message(buildVersion.version) def script_announceTime(self, gesture): """Announce current time""" import datetime ui.message(str(datetime.datetime.now().time()))
With __gestures, the docstring is used as the description. However, this makes it non-translatable unless you manually set the __doc__ attribute. The decorator approach is preferred.
# Window utility scripts for NVDA# Developer guide example 4import globalPluginHandlerfrom scriptHandler import scriptimport uiimport apiclass 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(f"class for {name} window: {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(f"Control ID for {name} window: {windowControlID}")
from scriptHandler import script, getLastScriptRepeatCountimport uiimport datetimeclass GlobalPlugin(globalPluginHandler.GlobalPlugin): @script( description="Report time/date", gesture="kb:NVDA+f12" ) def script_reportTimeOrDate(self, gesture): """Report time on first press, date on second press""" now = datetime.datetime.now() if getLastScriptRepeatCount() == 0: # First press: report time ui.message(now.strftime("%I:%M %p")) else: # Second press: report date ui.message(now.strftime("%A, %B %d, %Y"))
class MyDialog(NVDAObject): @script( description="Close dialog", gesture="kb:escape", canPropagate=True # Works on focused children too ) def script_closeDialog(self, gesture): # This runs even when a child control has focus ui.message("Closing dialog") # ... close logic
class GlobalPlugin(globalPluginHandler.GlobalPlugin): @script( description="Always-active script", gesture="kb:NVDA+escape", bypassInputHelp=True # Runs even in input help mode ) def script_emergency(self, gesture): # This runs even when input help (NVDA+1) is active ui.message("Emergency command")
class GlobalPlugin(globalPluginHandler.GlobalPlugin): @script( description="Wake up NVDA", gesture="kb:NVDA+shift+s", allowInSleepMode=True # Runs in sleep mode ) def script_toggleSleep(self, gesture): # This can run when NVDA is in sleep mode pass
@script( # Good - clear and specific description="Reports the current date and time",)def script_reportDateTime(self, gesture): pass# Bad - vague# description="Does something"
Choose Appropriate Gestures
Avoid conflicts with system or NVDA shortcuts:
# Good - uses NVDA keygesture="kb:NVDA+shift+t"# Bad - conflicts with copy# gesture="kb:control+c"
def script_example(self, gesture): try: # Do work result = doSomething() ui.message(result) except Exception: log.exception("Error in script_example") ui.message("An error occurred")