diff --git a/projectDocs/dev/developerGuide/developerGuide.md b/projectDocs/dev/developerGuide/developerGuide.md index 918f902bf6..3559a0932c 100644 --- a/projectDocs/dev/developerGuide/developerGuide.md +++ b/projectDocs/dev/developerGuide/developerGuide.md @@ -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} @@ -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` |`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} diff --git a/source/inputCore.py b/source/inputCore.py index 57f1814916..cf11d6efb4 100644 --- a/source/inputCore.py +++ b/source/inputCore.py @@ -452,6 +452,17 @@ 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 +@param scanCode: The scan code +@param extended: Whether this is an extended key +@param pressed: Whether this is a key press or release +@return: True to allow normal processing, False to block the key +""" + decide_executeGesture = extensionPoints.Decider() """ Notifies when a gesture is about to be executed, diff --git a/source/keyboardHandler.py b/source/keyboardHandler.py index e70879acb7..d06ddcf08c 100644 --- a/source/keyboardHandler.py +++ b/source/keyboardHandler.py @@ -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 \ @@ -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, \ diff --git a/source/speech/__init__.py b/source/speech/__init__.py index 61ea0d3573..5e31b01b00 100644 --- a/source/speech/__init__.py +++ b/source/speech/__init__.py @@ -63,7 +63,7 @@ spellTextInfo, splitTextIndentation, ) -from .extensions import speechCanceled +from .extensions import speechCanceled, speechPaused from .priorities import Spri from .types import ( @@ -142,6 +142,7 @@ "spellTextInfo", "splitTextIndentation", "speechCanceled", + "speechPaused", ] import synthDriverHandler diff --git a/source/speech/extensions.py b/source/speech/extensions.py index 13b2e42c4f..049c5118c6 100644 --- a/source/speech/extensions.py +++ b/source/speech/extensions.py @@ -22,6 +22,14 @@ Handlers are called without arguments. """ +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. diff --git a/source/speech/speech.py b/source/speech/speech.py index 32d3fd3cc0..3153208b8d 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -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, speechPaused, pre_speechCanceled, pre_speech from .extensions import filter_speechSequence from .commands import ( # Commands that are used in this file. @@ -211,6 +211,7 @@ def cancelSpeech(): def pauseSpeech(switch): getSynth().pause(switch) + speechPaused.notify(switch=switch) _speechState.isPaused = switch _speechState.beenCanceled = False diff --git a/tests/unit/test_speech.py b/tests/unit/test_speech.py index e91f4d3b12..fb0b60eaea 100644 --- a/tests/unit/test_speech.py +++ b/tests/unit/test_speech.py @@ -16,7 +16,9 @@ _getSpellingSpeechAddCharMode, _getSpellingSpeechWithoutCharMode, cancelSpeech, + pauseSpeech, speechCanceled, + speechPaused, ) from speech.commands import ( BeepCommand, @@ -591,3 +593,10 @@ def test_speechCanceledExtensionPoint(self): speechCanceled, ): cancelSpeech() + + def test_speechPausedExtensionPoint(self): + with actionTester(self, speechPaused, switch=True): + pauseSpeech(True) + + with actionTester(self, speechPaused, switch=False): + pauseSpeech(False) diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index 4fd9af5c4a..b40e644afe 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -99,6 +99,10 @@ Add-ons will need to be re-tested and have their manifest updated. * Removed the requirement to indent function parameter lists by two tabs from NVDA's Coding Standards, to be compatible with modern automatic linting. (#17126, @XLTechie) * Added the [VS Code workspace configuration for NVDA](https://nvaccess.org/nvaccess/vscode-nvda) as a git submodule. (#17003) * In the `brailleTables` module, a `getDefaultTableForCurrentLang` function has been added (#17222, @nvdaes) +* Added the following extension points: + * inputCore.decide_handleRawKey: called on each keypress + * speech.extensions.speechPaused: Called when speech is paused or unpaused + #### API Breaking Changes