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

# Event System

> Understanding NVDA's event handling architecture

NVDA's event system allows plugins and NVDA Objects to respond to changes in the GUI, such as focus changes, value updates, and state changes. Events are abstracted from various accessibility APIs into a unified system.

## How Events Work

When NVDA detects accessibility events from the operating system or applications, it:

1. **Receives the event** from the underlying API (IAccessible, UIA, etc.)
2. **Abstracts the event** into NVDA's internal format
3. **Propagates the event** through a chain of handlers
4. **Processes the event** to update speech, braille, and UI

## Event Propagation Chain

Events propagate through handlers in this order:

```mermaid theme={null}
graph TD
    A[Event Fired] --> B[Global Plugins]
    B --> C[App Module]
    C --> D[Tree Interceptor]
    D --> E[NVDA Object]
    E --> F[Event Processed]
```

<Steps>
  <Step title="Global Plugins">
    All loaded global plugins receive the event first
  </Step>

  <Step title="App Module">
    The app module for the object's application
  </Step>

  <Step title="Tree Interceptor">
    The tree interceptor (like browse mode) if applicable
  </Step>

  <Step title="NVDA Object">
    The NVDA Object itself that triggered the event
  </Step>
</Steps>

## Event Method Signatures

### On NVDA Objects

When defined directly on an NVDA Object:

```python Event on NVDA Object theme={null}
class MyNVDAObject(NVDAObject):
    def event_gainFocus(self):
        """Handle focus event on this object"""
        # self is the NVDA Object that gained focus
        import tones
        tones.beep(440, 50)
        # Must call parent to continue normal processing
        super().event_gainFocus()
```

### On Plugins and App Modules

When defined on global plugins, app modules, or tree interceptors:

```python Event on plugin theme={null}
import globalPluginHandler

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    def event_gainFocus(self, obj, nextHandler):
        """Handle focus events globally"""
        # obj: the NVDA Object that gained focus
        # nextHandler: function to call to propagate event
        
        # Do custom processing
        if obj.role == controlTypes.Role.BUTTON:
            import tones
            tones.beep(440, 50)
        
        # Always call nextHandler to continue propagation
        nextHandler()
```

<Warning>
  Always call `nextHandler()` unless you specifically want to stop event propagation. Failing to do so can break NVDA's normal event processing.
</Warning>

## Common Events

### Focus Events

```python Focus events theme={null}
import appModuleHandler
import ui

class AppModule(appModuleHandler.AppModule):
    
    def event_gainFocus(self, obj, nextHandler):
        """Object gained focus"""
        ui.message(f"{obj.name} focused")
        nextHandler()
    
    def event_loseFocus(self, obj, nextHandler):
        """Object lost focus"""
        # Usually less common to handle
        nextHandler()
    
    def event_focusEntered(self, obj, nextHandler):
        """Focus entered a container
        
        Fired when focus moves inside an ancestor object.
        For example, when tabbing into a dialog.
        """
        if obj.role == controlTypes.Role.DIALOG:
            ui.message(f"Entered {obj.name} dialog")
        nextHandler()
```

### Property Change Events

```python Property change events theme={null}
    def event_nameChange(self, obj, nextHandler):
        """Object's name changed"""
        ui.message(f"Name changed to {obj.name}")
        nextHandler()
    
    def event_valueChange(self, obj, nextHandler):
        """Object's value changed"""
        # Common for sliders, progress bars, edit fields
        if obj.role == controlTypes.Role.SLIDER:
            ui.message(f"{obj.value}")
        nextHandler()
    
    def event_stateChange(self, obj, nextHandler):
        """Object's state changed"""
        # Check specific state changes
        if controlTypes.State.CHECKED in obj.states:
            ui.message("Checked")
        nextHandler()
    
    def event_descriptionChange(self, obj, nextHandler):
        """Object's description changed"""
        nextHandler()
```

### Text and Caret Events

```python Text and caret events theme={null}
    def event_caret(self, obj, nextHandler):
        """Caret moved within object
        
        This is the insertion point in text fields.
        """
        # Get caret position
        try:
            info = obj.makeTextInfo(textInfos.POSITION_CARET)
            info.expand(textInfos.UNIT_CHARACTER)
            ui.message(info.text)
        except:
            pass
        nextHandler()
    
    def event_textChange(self, obj, nextHandler):
        """Text content changed"""
        # Useful for monitoring live regions
        nextHandler()
```

### Window Events

```python Window events theme={null}
    def event_foreground(self, obj, nextHandler):
        """Window came to foreground
        
        Fired when a window becomes the active top-level window.
        """
        ui.message(f"{obj.name} window")
        nextHandler()
    
    def event_locationChange(self, obj, nextHandler):
        """Object moved on screen"""
        # Rarely needed, mostly for tracking window movement
        nextHandler()
```

### Selection Events

```python Selection events theme={null}
    def event_selection(self, obj, nextHandler):
        """Item was selected"""
        # Common in lists, trees, combo boxes
        if obj.role == controlTypes.Role.LISTITEM:
            ui.message(f"Selected {obj.name}")
        nextHandler()
    
    def event_selectionAdd(self, obj, nextHandler):
        """Item added to selection (multi-select)"""
        nextHandler()
    
    def event_selectionRemove(self, obj, nextHandler):
        """Item removed from selection"""
        nextHandler()
    
    def event_selectionWithin(self, obj, nextHandler):
        """Selection moved within container"""
        nextHandler()
```

### Document Events

```python Document events theme={null}
    def event_documentLoadComplete(self, obj, nextHandler):
        """Document finished loading"""
        # Common for web pages
        ui.message("Page loaded")
        nextHandler()
    
    def event_pageChange(self, obj, nextHandler):
        """Page changed in document"""
        nextHandler()
```

### Application Events

```python Application events   theme={null}
    def event_appModule_gainFocus(self):
        """This application gained focus
        
        Special event only on app modules.
        Called when switching to this application.
        """
        ui.message("Application activated")
    
    def event_appModule_loseFocus(self):
        """This application lost focus
        
        Called when switching away from this application.
        """
        pass
```

## Real-World Examples

### Example 1: Beep on Focus Changes

From the NVDA Developer Guide:

```python Notepad focus beeps theme={null}
# Notepad App Module for NVDA
# Developer guide example 1

import appModuleHandler

class AppModule(appModuleHandler.AppModule):
    
    def event_gainFocus(self, obj, nextHandler):
        import tones
        tones.beep(550, 50)
        nextHandler()
```

### Example 2: Announce Button Presses

```python Announce button activation theme={null}
import appModuleHandler
import controlTypes
import ui

class AppModule(appModuleHandler.AppModule):
    
    def event_gainFocus(self, obj, nextHandler):
        # Announce when buttons are focused
        if obj.role == controlTypes.Role.BUTTON:
            ui.message(f"Button: {obj.name}")
        nextHandler()
```

### Example 3: Monitor Value Changes

```python Monitor progress bars theme={null}
import globalPluginHandler
import controlTypes
import ui

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def event_valueChange(self, obj, nextHandler):
        # Announce progress bar changes
        if obj.role == controlTypes.Role.PROGRESSBAR:
            try:
                value = int(obj.value.rstrip('%'))
                # Only announce every 10%
                if value % 10 == 0:
                    ui.message(f"{value}%")
            except:
                pass
        nextHandler()
```

### Example 4: Track Focus Entering Dialogs

```python Dialog tracking theme={null}
import appModuleHandler
import controlTypes
import tones

class AppModule(appModuleHandler.AppModule):
    
    def event_focusEntered(self, obj, nextHandler):
        # Play sound when entering a dialog
        if obj.role == controlTypes.Role.DIALOG:
            tones.beep(880, 100)
        nextHandler()
```

### Example 5: Live Region Monitoring

```python Live region monitoring theme={null}
import globalPluginHandler
import ui

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def __init__(self):
        super().__init__()
        self._lastLiveText = {}
    
    def event_liveRegionChange(self, obj, nextHandler):
        """Handle live region updates"""
        # Get text info
        try:
            info = obj.makeTextInfo(textInfos.POSITION_ALL)
            text = info.text
            
            # Only announce if different from last time
            objId = id(obj)
            if text != self._lastLiveText.get(objId):
                ui.message(text)
                self._lastLiveText[objId] = text
        except:
            pass
        
        nextHandler()
```

## Event Timing and Threading

Events are processed on NVDA's main thread:

```python Event timing theme={null}
import queueHandler
import time

class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    
    def event_gainFocus(self, obj, nextHandler):
        # Events execute on main thread
        # Don't do expensive operations here
        
        # For long operations, use background thread
        def backgroundWork():
            time.sleep(2)  # Long operation
            # Queue result back to main thread
            queueHandler.queueFunction(
                queueHandler.eventQueue,
                ui.message,
                "Work complete"
            )
        
        import threading
        threading.Thread(target=backgroundWork).start()
        
        nextHandler()
```

<Warning>
  Do not perform long-running operations in event handlers as this will freeze NVDA. Use background threads for expensive operations.
</Warning>

## Stopping Event Propagation

Sometimes you want to stop an event from propagating further:

```python Stopping propagation theme={null}
class AppModule(appModuleHandler.AppModule):
    
    def event_gainFocus(self, obj, nextHandler):
        # Check if we want to suppress this event
        if obj.role == controlTypes.Role.UNKNOWN:
            # Don't call nextHandler - stops propagation
            # Event won't reach NVDA Object or core
            return
        
        # Normal processing
        nextHandler()
```

<Warning>
  Stopping propagation prevents NVDA's core event processing. Only do this if you're replacing NVDA's default behavior entirely.
</Warning>

## Event Filtering

Filter events before processing:

```python Event filtering theme={null}
class AppModule(appModuleHandler.AppModule):
    
    def event_valueChange(self, obj, nextHandler):
        # Ignore value changes from progress bars in background
        from api import getFocusObject
        focus = getFocusObject()
        
        if obj.role == controlTypes.Role.PROGRESSBAR:
            # Only announce if it's in the focus ancestry
            import api
            if obj not in api.getFocusAncestors() and obj != focus:
                nextHandler()
                return
        
        # Process normally
        nextHandler()
```

## Custom Events

You can fire custom events:

```python Custom events theme={null}
import eventHandler

# Fire a custom event
eventHandler.queueEvent("customEvent", obj)

# Handle custom event
class GlobalPlugin(globalPluginHandler.GlobalPlugin):
    def event_customEvent(self, obj, nextHandler):
        ui.message("Custom event received")
        nextHandler()
```

## Event Queuing

Events are queued to prevent flooding:

```python Event queuing theme={null}
import eventHandler

# Queue an event
eventHandler.queueEvent("gainFocus", obj)

# Events are processed in order on the event queue
# Multiple identical events may be coalesced
```

## Debugging Events

Log events for debugging:

```python Event debugging theme={null}
import appModuleHandler
from logHandler import log

class AppModule(appModuleHandler.AppModule):
    
    def event_gainFocus(self, obj, nextHandler):
        # Log event details
        log.debug(
            f"Focus event: {obj.name}, "
            f"role={obj.role}, "
            f"class={obj.windowClassName}"
        )
        nextHandler()
```

<Tip>
  Enable debug logging in NVDA settings to see detailed event information: NVDA menu → Tools → Reload plugins
</Tip>

## Performance Considerations

<AccordionGroup>
  <Accordion title="Avoid Expensive Operations">
    Event handlers should be fast:

    ```python theme={null}
    def event_gainFocus(self, obj, nextHandler):
        # Bad - expensive operation
        # all_text = obj.makeTextInfo(textInfos.POSITION_ALL).text
        
        # Good - just access properties
        name = obj.name
        nextHandler()
    ```
  </Accordion>

  <Accordion title="Cache Expensive Computations">
    Use properties with caching:

    ```python theme={null}
    class MyObject(NVDAObject):
        _cache_expensiveProperty = True
        
        def _get_expensiveProperty(self):
            # Cached for the core pump cycle
            return self._computeExpensive()
    ```
  </Accordion>

  <Accordion title="Limit Event Frequency">
    Throttle high-frequency events:

    ```python theme={null}
    import time

    class GlobalPlugin(globalPluginHandler.GlobalPlugin):
        def __init__(self):
            super().__init__()
            self._lastValueChangeTime = 0
        
        def event_valueChange(self, obj, nextHandler):
            now = time.time()
            # Only process every 100ms
            if now - self._lastValueChangeTime < 0.1:
                nextHandler()
                return
            
            self._lastValueChangeTime = now
            # Process event
            nextHandler()
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

1. **Always call nextHandler()** unless you're intentionally stopping propagation
2. **Keep handlers fast** - use background threads for expensive operations
3. **Filter early** - check conditions before doing work
4. **Use appropriate event types** - don't handle all events when you need specific ones
5. **Log for debugging** - use log.debug() to trace event flow
6. **Test thoroughly** - events fire frequently, ensure your code is efficient

## See Also

<CardGroup cols={2}>
  <Card title="NVDA Objects" icon="cube" href="/development/architecture/nvda-objects">
    Learn about the objects that fire events
  </Card>

  <Card title="Scripts & Gestures" icon="keyboard" href="/development/architecture/scripts-and-gestures">
    Handle user input with scripts
  </Card>

  <Card title="Writing Plugins" icon="code" href="/development/plugins">
    Create plugins that handle events
  </Card>

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