Skip to content

Commit 5f01ca6

Browse files
jcstehSascha Cowley
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 ed64703 commit 5f01ca6

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.
@@ -281,11 +284,15 @@ def internal_keyUpEvent(vkCode,scanCode,extended,injected):
281284
"""
282285
try:
283286
global lastNVDAModifier, lastNVDAModifierReleaseTime, bypassNVDAModifier, passKeyThroughCount, lastPassThroughKeyDown, currentModifiers
284-
# Injected keys should be ignored in some cases.
285-
if injected and (ignoreInjected or not config.conf['keyboard']['handleInjectedKeys']):
286-
return True
287-
288287
keyCode = (vkCode, extended)
288+
# Injected keys should be ignored in some cases.
289+
if injected:
290+
if not config.conf['keyboard']['handleInjectedKeys']:
291+
return True
292+
if ignoreInjected:
293+
if keyCode == _lastInjectedKeyUp:
294+
winKernel.kernel32.SetEvent(_injectionDoneEvent)
295+
return True
289296

290297
if passKeyThroughCount >= 1:
291298
if lastPassThroughKeyDown == keyCode:
@@ -563,7 +570,12 @@ def executeScript(self, script):
563570
# Now actually execute the script.
564571
super().executeScript(script)
565572

573+
#: The maximum amount of time (in ms) to wait for keys injected by NVDA to be
574+
#: received by NVDA.
575+
_INJECTION_WAIT_TIMEOUT: int = 10
576+
566577
def send(self):
578+
global _lastInjectedKeyUp, _injectionDoneEvent
567579
keys = []
568580
for vk, ext in self.generalizedModifiers:
569581
if vk == VK_WIN:
@@ -578,6 +590,11 @@ def send(self):
578590
keys.append((self.vkCode, self.scanCode, self.isExtended))
579591

580592
with ignoreInjection():
593+
handleInjectedKeys = config.conf['keyboard']['handleInjectedKeys']
594+
if handleInjectedKeys:
595+
_lastInjectedKeyUp = (keys[0][0], keys[0][2])
596+
if not _injectionDoneEvent:
597+
_injectionDoneEvent = winKernel.createEvent()
581598
if winUser.getKeyState(self.vkCode) & 32768:
582599
# This key is already down, so send a key up for it first.
583600
winUser.keybd_event(self.vkCode, self.scanCode, self.isExtended + 2, 0)
@@ -588,6 +605,11 @@ def send(self):
588605
# Send key up events for the keys in reverse order.
589606
for vk, scan, ext in reversed(keys):
590607
winUser.keybd_event(vk, scan, ext + 2, 0)
608+
if handleInjectedKeys:
609+
# Wait for the keys to be received by NVDA. We don't do this if
610+
# handleInjectedKeys is disabled because we just ignore all injected keys
611+
# in that case.
612+
winKernel.waitForSingleObject(_injectionDoneEvent, self._INJECTION_WAIT_TIMEOUT)
591613

592614
@classmethod
593615
def fromName(cls, name):

‎user_docs/en/changes.t2t‎

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

1313

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

1618

1719
== Changes for Developers ==

0 commit comments

Comments
 (0)