Skip to content

Commit

Permalink
Add new extension points for keyboard input and speech pause (#17428)
Browse files Browse the repository at this point in the history
This commit introduces two new extension points:

inputCore.decide_handleRawKey
Called before any NVDA processing of raw keyboard events
Allows add-ons to intercept and optionally block keyboard events
Provides full context: vkCode, scanCode, extended flag, and press/release state
Implemented in both keyDown and keyUp event handlers
Returns True to allow normal processing, False to block the key
speech.extensions.speechPaused
Notifies when speech is paused or resumed
Provides boolean 'switch' parameter (True=paused, False=resumed)
Added corresponding unit tests to verify functionality
Integrated into existing pauseSpeech() function
Technical Details:

Added documentation for both extension points in developerGuide.md
Updated speech/init.py to expose the new speechPaused extension point
Added test case in test_speech.py to verify extension point behavior
Maintains backwards compatibility with existing extensions
These additions enable add-ons to:

Implement advanced keyboard interception/modification
React to speech pause state changes
Link to issue number:
N/A - New feature addition for extensibility

Summary of the issue:
NVDA needed additional extension points to allow add-ons to:

Intercept and control raw keyboard events before NVDA processing
Monitor and react to speech pause state changes
Description of user facing changes
No direct user-facing changes
Enables add-on developers to create more sophisticated keyboard handling and speech feedback features
All changes are API-level additions that maintain backwards compatibility
Description of development approach
Raw Keyboard Extension Point:

Added decide_handleRawKey extension point in inputCore.py
Integrated into both keyDown and keyUp event handlers in keyboardHandler.py
Full keyboard event context provided (vkCode, scanCode, extended, pressed)
Boolean return value controls event propagation
Speech Pause Extension Point:

Added speechPaused extension point in speech/extensions.py
Integrated into existing pauseSpeech() function
Provides pause state through boolean parameter
Documentation:

Added entries in developerGuide.md
Updated relevant module documentation
Added changelog entry
  • Loading branch information
ctoth authored Dec 4, 2024
1 parent d45008f commit 6c711fd
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 3 deletions.
2 changes: 2 additions & 0 deletions projectDocs/dev/developerGuide/developerGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ For examples of how to define and use new extension points, please see the code

| Type |Extension Point |Description|
|---|---|---|
|`Decider` |`decide_handleRawKey` |Notifies when a raw keyboard event is received, before any NVDA processing, allowing other code to decide if it should be handled.|
|`Decider` |`decide_executeGesture` |Notifies when a gesture is about to be executed, allowing other code to decide if it should be.|

### logHandler {#logHandlerExtPts}
Expand All @@ -1382,6 +1383,7 @@ For examples of how to define and use new extension points, please see the code
|`Action` |`speechCanceled` |Triggered when speech is canceled.|
|`Action` |`pre_speechCanceled` |Triggered before speech is canceled.|
|`Action` |`pre_speech` |Triggered before NVDA handles prepared speech.|
|`Action` |`post_speechPaused` |Triggered when speech is paused or resumed.|
|`Filter` |`filter_speechSequence` |Allows components or add-ons to filter speech sequence before it passes to the synth driver.|

### synthDriverHandler {#synthDriverHandlerExtPts}
Expand Down
16 changes: 16 additions & 0 deletions source/inputCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,22 @@ def __eq__(self, other: Any) -> bool:
return NotImplemented


decide_handleRawKey = extensionPoints.Decider()
"""
Notifies when a raw keyboard event is received, before any NVDA processing.
Handlers can decide whether the key should be processed by NVDA and/or passed to the OS.
:param vkCode: The virtual key code
:type vkCode: int
:param scanCode: The scan code
:type scanCode: int
:param extended: Whether this is an extended key
:type extended: bool
:param pressed: Whether this is a key press or release
:type pressed: bool
:return: True to allow normal processing, False to block the key
:rtype: bool
"""

decide_executeGesture = extensionPoints.Decider()
"""
Notifies when a gesture is about to be executed,
Expand Down
14 changes: 14 additions & 0 deletions source/keyboardHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ def shouldUseToUnicodeEx(focus: Optional["NVDAObject"] = None):

def internal_keyDownEvent(vkCode, scanCode, extended, injected):
"""Event called by winInputHook when it receives a keyDown."""
if not inputCore.decide_handleRawKey.decide(
vkCode=vkCode,
scanCode=scanCode,
extended=extended,
pressed=True,
):
return False
gestureExecuted = False
try:
global \
Expand Down Expand Up @@ -313,6 +320,13 @@ def internal_keyDownEvent(vkCode, scanCode, extended, injected):

def internal_keyUpEvent(vkCode, scanCode, extended, injected):
"""Event called by winInputHook when it receives a keyUp."""
if not inputCore.decide_handleRawKey.decide(
vkCode=vkCode,
scanCode=scanCode,
extended=extended,
pressed=False,
):
return False
try:
global \
lastNVDAModifier, \
Expand Down
3 changes: 2 additions & 1 deletion source/speech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
spellTextInfo,
splitTextIndentation,
)
from .extensions import speechCanceled
from .extensions import speechCanceled, post_speechPaused
from .priorities import Spri

from .types import (
Expand Down Expand Up @@ -142,6 +142,7 @@
"spellTextInfo",
"splitTextIndentation",
"speechCanceled",
"post_speechPaused",
]

import synthDriverHandler
Expand Down
8 changes: 8 additions & 0 deletions source/speech/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@
Handlers are called without arguments.
"""

post_speechPaused = Action()
"""
Notifies when speech is paused.
:param switch: True if speech is paused, False if speech is resumed.
:type switch: bool
"""

pre_speech = Action()
"""
Notifies when code attempts to speak text.
Expand Down
3 changes: 2 additions & 1 deletion source/speech/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from textUtils import unicodeNormalize
from textUtils.uniscribe import splitAtCharacterBoundaries
from . import manager
from .extensions import speechCanceled, pre_speechCanceled, pre_speech
from .extensions import speechCanceled, post_speechPaused, pre_speechCanceled, pre_speech
from .extensions import filter_speechSequence
from .commands import (
# Commands that are used in this file.
Expand Down Expand Up @@ -211,6 +211,7 @@ def cancelSpeech():

def pauseSpeech(switch):
getSynth().pause(switch)
post_speechPaused.notify(switch=switch)
_speechState.isPaused = switch
_speechState.beenCanceled = False

Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
_getSpellingSpeechAddCharMode,
_getSpellingSpeechWithoutCharMode,
cancelSpeech,
pauseSpeech,
speechCanceled,
post_speechPaused,
)
from speech.commands import (
BeepCommand,
Expand Down Expand Up @@ -591,3 +593,10 @@ def test_speechCanceledExtensionPoint(self):
speechCanceled,
):
cancelSpeech()

def test_post_speechPausedExtensionPoint(self):
with actionTester(self, post_speechPaused, switch=True):
pauseSpeech(True)

with actionTester(self, post_speechPaused, switch=False):
pauseSpeech(False)
4 changes: 3 additions & 1 deletion user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ Add-ons will need to be re-tested and have their manifest updated.
* Retrieving the `labeledBy` property now works for:
* objects in applications implementing the `labelled-by` IAccessible2 relation. (#17436, @michaelweghorn)
* UIA elements supporting the corresponding `LabeledBy` UIA property. (#17442, @michaelweghorn)

* Added the following extension points (#17428, @ctoth):
* `inputCore.decide_handleRawKey`: called on each keypress
* `speech.extensions.post_speechPaused`: called when speech is paused or unpaused

#### API Breaking Changes

Expand Down

0 comments on commit 6c711fd

Please sign in to comment.