> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nvaccess/nvda/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripts and Gestures

> 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.

## What are Scripts?

A **script** is a Python method that:

* Has a name starting with `script_`
* Takes a `gesture` parameter
* Is bound to one or more input gestures
* Can be assigned to a category for organization

```python Basic script structure theme={null}
from scriptHandler import script
import ui

class MyObject(ScriptableObject):
    
    @script(
        description="Says hello to the user",
        gesture="kb:NVDA+h"
    )
    def script_sayHello(self, gesture):
        ui.message("Hello!")
```

## What are Gestures?

A **gesture** is user input that can trigger a script:

* **Keyboard**: `kb:NVDA+t`, `kb:control+shift+a`
* **Braille display**: `br(freedomScientific):leftWizWheelUp`
* **Touch screen**: `ts:2finger_flickRight`
* **Braille keyboard**: `bk:space+dot1+dot3`

Gesture identifiers have the format:

```
<source>[(device)]:<key1>+<key2>+...
```

## The Script Decorator

The modern way to define scripts uses the `@script` decorator from `scriptHandler`:

```python Script decorator syntax theme={null}
from scriptHandler import script
import inputCore

class 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')}")
```

### Decorator Parameters

<ParamField path="description" type="string" required>
  Translatable description shown to users in the input gestures dialog
</ParamField>

<ParamField path="category" type="string">
  Category for grouping related scripts. Use constants from `inputCore` like:

  * `SCRCAT_BROWSE` - Browse mode
  * `SCRCAT_MISC` - Miscellaneous
  * `SCRCAT_SPEECH` - Speech
  * `SCRCAT_SYSTEM` - System
  * `SCRCAT_CONFIG` - Configuration
</ParamField>

<ParamField path="gesture" type="string">
  Single gesture identifier to bind
</ParamField>

<ParamField path="gestures" type="list">
  List of gesture identifiers to bind
</ParamField>

<ParamField path="canPropagate" type="boolean" default="false">
  Whether script applies to focus ancestor objects
</ParamField>

<ParamField path="bypassInputHelp" type="boolean" default="false">
  Whether script runs when input help is active
</ParamField>

<ParamField path="allowInSleepMode" type="boolean" default="false">
  Whether script runs in sleep mode
</ParamField>

<ParamField path="resumeSayAllMode" type="int">
  Say all mode to resume after script executes (from `sayAll.CURSOR_*` constants)
</ParamField>

<ParamField path="speakOnDemand" type="boolean" default="false">
  Whether script produces speech in "on-demand" mode
</ParamField>

## Gesture Binding Methods

### Method 1: Script Decorator (Recommended)

```python Using decorator theme={null}
from scriptHandler import script

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Announce NVDA version",
        gesture="kb:NVDA+shift+v"
    )
    def script_announceVersion(self, gesture):
        import buildVersion
        ui.message(buildVersion.version)
```

### Method 2: \_\_gestures Dictionary

```python Using __gestures dictionary theme={null}
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()))
```

<Warning>
  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.
</Warning>

### Method 3: Dynamic Binding

```python Dynamic binding theme={null}
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def __init__(self):
        super().__init__()
        # Bind gestures dynamically
        self.bindGesture("kb:NVDA+shift+v", "announceVersion")
    
    def script_announceVersion(self, gesture):
        import buildVersion
        ui.message(buildVersion.version)
```

## Script Resolution Order

When a user presses a key, NVDA searches for a matching script in this order:

<Steps>
  <Step title="User Gesture Map">
    User's custom key bindings from the input gestures dialog
  </Step>

  <Step title="Locale Gesture Map">
    Locale-specific gesture remappings
  </Step>

  <Step title="Braille Display Gestures">
    Gestures from the active braille display driver
  </Step>

  <Step title="Global Plugins">
    Scripts defined in loaded global plugins
  </Step>

  <Step title="App Module">
    Scripts in the app module for the active application
  </Step>

  <Step title="Tree Interceptor">
    Scripts in the tree interceptor (e.g., browse mode)
  </Step>

  <Step title="NVDA Object with Focus">
    Scripts on the focused NVDA Object
  </Step>

  <Step title="Focus Ancestors">
    Scripts on parent objects (if `canPropagate=True`)
  </Step>

  <Step title="Global Commands">
    Built-in NVDA commands
  </Step>
</Steps>

<Info>
  The first matching script is executed and the search stops. This allows plugins to override built-in commands by defining them earlier in the chain.
</Info>

## Real-World Examples

### Example 1: Simple Global Command

From the NVDA Developer Guide:

```python Version announcement theme={null}
# Version announcement plugin for NVDA
# Developer guide example 3

import globalPluginHandler
from scriptHandler import script
import ui
import buildVersion

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(gesture="kb:NVDA+shift+v")
    def script_announceNVDAVersion(self, gesture):
        ui.message(buildVersion.version)
```

### Example 2: Window Information Scripts

From the NVDA Developer Guide:

```python Window utility scripts theme={null}
# Window utility scripts for NVDA
# Developer guide example 4

import globalPluginHandler
from scriptHandler import script
import ui
import api

class 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}")
```

### Example 3: Object-Specific Script

From the NVDA Developer Guide:

```python Enhanced edit field theme={null}
import appModuleHandler
from scriptHandler import script
from NVDAObjects.IAccessible import IAccessible
import controlTypes
import ui

class AppModule(appModuleHandler.AppModule):
    
    def chooseNVDAObjectOverlayClasses(self, obj, clsList):
        if obj.windowClassName == "Edit" and obj.role == controlTypes.Role.EDITABLETEXT:
            clsList.insert(0, EnhancedEditField)

class EnhancedEditField(IAccessible):
    
    @script(gesture="kb:NVDA+l")
    def script_reportLength(self, gesture):
        """Report the number of characters in this edit field"""
        ui.message(f"{len(self.value)}")
```

<Note>
  This script only works when an edit field is focused because it's defined on the `EnhancedEditField` class.
</Note>

### Example 4: Script with Multiple Gestures

```python Multiple gestures theme={null}
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Report current time",
        gestures=[
            "kb:NVDA+f12",
            "kb:NVDA+shift+t",
            "br(freedomScientific):leftWizWheelDown"
        ]
    )
    def script_reportTime(self, gesture):
        import datetime
        now = datetime.datetime.now()
        ui.message(now.strftime("%I:%M %p"))
```

### Example 5: Script with Repeat Detection

```python Repeat detection theme={null}
from scriptHandler import script, getLastScriptRepeatCount
import ui
import datetime

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

## Gesture Identifier Format

### Keyboard Gestures

```python Keyboard gesture examples theme={null}
# Format: kb[(layout)]:<modifiers>+<key>

"kb:NVDA+t"                 # NVDA key + t
"kb:control+shift+a"        # Ctrl+Shift+A
"kb:alt+f4"                 # Alt+F4
"kb(laptop):NVDA+t"         # Laptop layout specific
"kb:NVDA+shift+upArrow"     # With arrow keys
"kb:applications"           # Applications (context menu) key
"kb:escape"                 # Escape key
```

### Braille Display Gestures

```python Braille display gestures theme={null}
# Format: br(<driver>):<button>

"br(freedomScientific):leftWizWheelUp"
"br(alva.BC640):t3"
"br(baum):d1"
"br(brailliant):space+dot1+dot3"
```

### Touch Screen Gestures

```python Touch gestures theme={null}
# Format: ts:<gesture>

"ts:2finger_flickRight"
"ts:3finger_tap"
"ts:flickLeft"
"ts:tap"
```

## Script Categories

Organize scripts into categories for the input gestures dialog:

```python Script categories theme={null}
import inputCore

# Built-in categories
inputCore.SCRCAT_BROWSE      # Browse mode
inputCore.SCRCAT_SPEECH       # Speech
inputCore.SCRCAT_BRAILLE      # Braille
inputCore.SCRCAT_VISION       # Vision
inputCore.SCRCAT_FOCUS        # Focus
inputCore.SCRCAT_SYSTEM       # System
inputCore.SCRCAT_CONFIG       # Configuration
inputCore.SCRCAT_MISC         # Miscellaneous
inputCore.SCRCAT_KBEMU        # Keyboard emulation

# Or set on the class
class MyPlugin(globalPluginHandler.GlobalPlugin):
    scriptCategory = "My Plugin"
    
    @script(description="Do something")
    def script_doSomething(self, gesture):
        pass
```

## Advanced Features

### Propagating Scripts

Allow scripts to work on focus ancestors:

```python Propagating script theme={null}
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
```

### Bypassing Input Help

```python Bypass input help theme={null}
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")
```

### Sleep Mode Scripts

```python Sleep mode script theme={null}
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
```

### Gesture Pass-Through

Pass gesture to the application:

```python Pass-through theme={null}
class AppModule(appModuleHandler.AppModule):
    
    @script(
        description="Conditional pass-through",
        gesture="kb:control+c"
    )
    def script_smartCopy(self, gesture):
        # Do custom processing first
        focusObj = api.getFocusObject()
        
        if shouldInterceptCopy(focusObj):
            # Do custom copy
            performCustomCopy()
        else:
            # Pass gesture to application
            gesture.send()
```

## Working with Gesture Objects

The `gesture` parameter provides information about the input:

```python Gesture object theme={null}
def script_example(self, gesture):
    # Gesture properties
    displayName = gesture.displayName  # Human-readable name
    identifiers = gesture.identifiers  # All matching identifiers
    
    # Send gesture to application
    gesture.send()
    
    # Check gesture type
    if gesture.isModifier:
        ui.message("Modifier key pressed")
    
    # Access keyboard-specific info
    if hasattr(gesture, 'vkCode'):
        vkCode = gesture.vkCode
        scanCode = gesture.scanCode
```

## Unbinding Gestures

Remove gesture bindings:

```python Unbinding gestures theme={null}
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def __init__(self):
        super().__init__()
        # Remove a gesture binding
        try:
            self.removeGestureBinding("kb:NVDA+t")
        except LookupError:
            pass  # Gesture wasn't bound
    
    # Or unbind in __gestures
    __gestures = {
        "kb:NVDA+t": None,  # Unbinds this gesture
    }
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use Descriptive Names">
    Script names should be clear and descriptive:

    ```python theme={null}
    # Good
    def script_announceWindowTitle(self, gesture):
        pass

    # Bad
    def script_dwt(self, gesture):
        pass
    ```
  </Accordion>

  <Accordion title="Provide Good Descriptions">
    Write clear, translatable descriptions:

    ```python theme={null}
    @script(
        # Good - clear and specific
        description="Reports the current date and time",
    )
    def script_reportDateTime(self, gesture):
        pass

    # Bad - vague
    # description="Does something"
    ```
  </Accordion>

  <Accordion title="Choose Appropriate Gestures">
    Avoid conflicts with system or NVDA shortcuts:

    ```python theme={null}
    # Good - uses NVDA key
    gesture="kb:NVDA+shift+t"

    # Bad - conflicts with copy
    # gesture="kb:control+c"
    ```
  </Accordion>

  <Accordion title="Use Categorization">
    Assign appropriate categories:

    ```python theme={null}
    @script(
        category=inputCore.SCRCAT_MISC,
        description="My script"
    )
    def script_myScript(self, gesture):
        pass
    ```
  </Accordion>

  <Accordion title="Handle Errors Gracefully">
    Scripts should not crash:

    ```python theme={null}
    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")
    ```
  </Accordion>
</AccordionGroup>

## Testing Scripts

```python Testing scripts theme={null}
# 1. Enable developer scratchpad in NVDA settings
# 2. Create globalPlugins/myTest.py:

import globalPluginHandler
from scriptHandler import script
import ui
from logHandler import log

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    @script(
        description="Test script",
        gesture="kb:NVDA+shift+f12"
    )
    def script_test(self, gesture):
        log.info("Test script executed")
        ui.message("Test!")

# 3. Reload plugins: NVDA menu → Tools → Reload plugins
# 4. Test the gesture
# 5. Check log: NVDA menu → Tools → View log
```

## See Also

<CardGroup cols={2}>
  <Card title="NVDA Objects" icon="cube" href="/development/architecture/nvda-objects">
    Define scripts on NVDA Objects
  </Card>

  <Card title="Events" icon="bolt" href="/development/architecture/events">
    React to system events
  </Card>

  <Card title="Writing Plugins" icon="code" href="/development/plugins">
    Create plugins with scripts
  </Card>

  <Card title="Architecture Overview" icon="sitemap" href="/development/architecture/overview">
    Understand the system design
  </Card>
</CardGroup>
