Skip to content

Commit 8141bf0

Browse files
jcstehAdriani90
authored andcommitted
Wait for sent keys to be picked up by the keyboard hook. (nvaccess#15957)
Fixes nvaccess#15822. Summary of the issue: With "Handle keys from other applications" enabled, NVDA interferes with certain software which intercepts and re-transmits keyboard commands such as Nudi6.1. For example, this makes it impossible to use backspace while this software is running. Description of user facing changes Backspace now works correctly when using Nudi6.1 with NVDA's "Handle keys from other applications" setting enabled. Description of development approach Before nvaccess#14708, we delayed slightly when sending keys, which would mean that any keys sent by an app which captured a key sent by NVDA would be ignored during that period. After nvaccess#14708, we only ignore any keys received while NVDA is sending keys, but that doesn't include any keys captured and sent afterward by, for example, Nudi6.1. This could result in a loop where NVDA kept receiving the key it sent (re-transmitted by the other app) and sending it again. To fix this, we now explicitly wait for the key sent by NVDA (e.g. backspace) to be received by the keyboard hook before we stop ignoring keys and thus return from KeyboardInputGesture.send(). This means that if another application intercepts this key and re-transmits it, we will wait for that re-transmission and ignore it. We use a kernel event so that the notification from the keyboard hook can be handled as quickly as possible without being dependent on the system timer resolution. There is a chance that a key will be intercepted and never re-sent. To deal with this, we wait for a maximum timeout of 10 ms. This means that in the best case scenario, nothing intercepts a key sent by NVDA and we return almost immediately. In the worst case scenario, we wait between 10 and 30 ms (depending on the system timer resolution), which is no worse than the situation before nvaccess#14708.
1 parent f796908 commit 8141bf0

2 files changed

Lines changed: 28 additions & 4 deletions

File tree

‎source/keyboardHandler.py‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@
3535
import NVDAState
3636
from contextlib import contextmanager
3737
import threading
38+
import winKernel
3839

3940
if typing.TYPE_CHECKING:
4041
from NVDAObjects import NVDAObject # noqa: F401
4142
from watchdog import WatchdogObserver
4243

4344
_watchdogObserver: typing.Optional["WatchdogObserver"] = None
4445
ignoreInjected=False
46+
_lastInjectedKeyUp: tuple[int, int] | None = None
47+
_injectionDoneEvent: int | None = None
4548

4649
# Fake vk codes.
4750
# These constants should be assigned to the name that NVDA will use for the key.
@@ -285,11 +288,15 @@ def internal_keyUpEvent(vkCode,scanCode,extended,injected):
285288
"""
286289
try:
287290
global lastNVDAModifier, lastNVDAModifierReleaseTime, bypassNVDAModifier, passKeyThroughCount, lastPassThroughKeyDown, currentModifiers
288-
# Injected keys should be ignored in some cases.
289-
if injected and (ignoreInjected or not config.conf['keyboard']['handleInjectedKeys']):
290-
return True
291-
292291
keyCode = (vkCode, extended)
292+
# Injected keys should be ignored in some cases.
293+
if injected:
294+
if not config.conf['keyboard']['handleInjectedKeys']:
295+
return True
296+
if ignoreInjected:
297+
if keyCode == _lastInjectedKeyUp:
298+
winKernel.kernel32.SetEvent(_injectionDoneEvent)
299+
return True
293300

294301
if passKeyThroughCount >= 1:
295302
if lastPassThroughKeyDown == keyCode:
@@ -567,7 +574,12 @@ def executeScript(self, script):
567574
# Now actually execute the script.
568575
super().executeScript(script)
569576

577+
#: The maximum amount of time (in ms) to wait for keys injected by NVDA to be
578+
#: received by NVDA.
579+
_INJECTION_WAIT_TIMEOUT: int = 10
580+
570581
def send(self):
582+
global _lastInjectedKeyUp, _injectionDoneEvent
571583
keys = []
572584
for vk, ext in self.generalizedModifiers:
573585
if vk == VK_WIN:
@@ -582,6 +594,11 @@ def send(self):
582594
keys.append((self.vkCode, self.scanCode, self.isExtended))
583595

584596
with ignoreInjection():
597+
handleInjectedKeys = config.conf['keyboard']['handleInjectedKeys']
598+
if handleInjectedKeys:
599+
_lastInjectedKeyUp = (keys[0][0], keys[0][2])
600+
if not _injectionDoneEvent:
601+
_injectionDoneEvent = winKernel.createEvent()
585602
if winUser.getKeyState(self.vkCode) & 32768:
586603
# This key is already down, so send a key up for it first.
587604
winUser.keybd_event(self.vkCode, self.scanCode, self.isExtended + 2, 0)
@@ -592,6 +609,11 @@ def send(self):
592609
# Send key up events for the keys in reverse order.
593610
for vk, scan, ext in reversed(keys):
594611
winUser.keybd_event(vk, scan, ext + 2, 0)
612+
if handleInjectedKeys:
613+
# Wait for the keys to be received by NVDA. We don't do this if
614+
# handleInjectedKeys is disabled because we just ignore all injected keys
615+
# in that case.
616+
winKernel.waitForSingleObject(_injectionDoneEvent, self._INJECTION_WAIT_TIMEOUT)
595617

596618
@classmethod
597619
def fromName(cls, name):

‎user_docs/en/changes.t2t‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ What's New in NVDA
1313

1414

1515
== Bug Fixes ==
16+
- Backspace now works correctly when using Nudi 6.1 with NVDA's "Handle keys from other applications" setting enabled. (#15822, @jcsteh)
17+
-
1618

1719

1820
== Changes for Developers ==

0 commit comments

Comments
 (0)