Is your feature request related to a problem? Please describe
When caps lock is an NVDA modifier key and a full screen Remote Desktop session has focus,
quickly repeated caps lock presses toggle the local caps lock state unintentionally.
When the remote session also runs NVDA with caps lock as its modifier, the two machines end up
with different caps lock states.
The cause is that the RDP client swallows all physical keys in full screen mode and feeds caps
lock presses back into the client system as injected input.
NVDA processes that echo as its NVDA modifier, and two echoes within the multi-press timeout
trigger the double-press bypass, toggling caps lock on the client only.
A full analysis is under additional context below.
Add-ons cannot address this precisely, because no extension point exposes whether a key event
was injected: inputCore.decide_handleRawKey receives vkCode, scanCode, extended and
pressed, but not the injected flag the low level hook already has.
Diagnosing the problem also required monkeypatching winInputHook, for the same reason.
Describe the solution you'd like
Pass injected in the inputCore.decide_handleRawKey.decide(...) calls in
keyboardHandler.internal_keyDownEvent and internal_keyUpEvent.
This is backward compatible: extension point handlers only receive the keyword arguments they
declare, and an add-on handler can declare injected=None to keep working on older NVDA
versions.
With that flag, a remote desktop add-on can swallow the injected caps lock echo precisely.
Since decide_handleRawKey is the only extension point that fires before the "Handle keys from
other applications" check, this also covers users who have that setting disabled.
Describe alternatives you've considered
- An mstsc app module that swallows the injected caps lock: an app module is per executable
(msrdc, Citrix and others would need their own), and without an add-on that has a channel to
the server it only suppresses the spurious toggle. It might even make things worse, since the
echo is the client's only way to keep the local state in step with the session.
- A gesture level veto in the add-on, gated on a full screen window heuristic: this is what
RDAccess currently implements. It works, but only
matches the default "Apply Windows key combinations: only when using the full screen"
configuration, and it cannot see at the gesture stage whether a key was injected.
Additional context
Reproduce the underlying problem by setting caps lock as an NVDA modifier key on both ends of a
full screen mstsc session and pressing caps lock+down arrow twice within about half a second:
the client toggles and announces caps lock, while the server does not change.
Both machines were instrumented with a wrapper around the low level keyboard hook, logging each
event's LLKHF_INJECTED flag, NVDA's trap or pass decision, and the toggle state. Observed:
- In a focused full screen session, NVDA's hook receives no physical keys at all. mstsc
installs its own low level hook, which runs before NVDA's, swallows every key, and forwards
it to the server.
- About 115 ms after each physical caps lock transition, NVDA's hook receives an injected caps
lock event mirroring the physical timing. Only caps lock is echoed; other keys are not.
- NVDA processes the echo as its NVDA modifier and traps it. The other keys are invisible, so
nothing clears lastNVDAModifier. An echo within multiPressTimeout of the previous one
therefore triggers the double-press bypass in keyboardHandler.internal_keyDownEvent: the
key passes to the OS and the local caps lock toggles.
- The server receives the forwarded caps lock as ordinary input and traps it as its own
modifier. The companion key clears its lastNVDAModifier, so the server never toggles.
- With "Handle keys from other applications" disabled, NVDA ignores the echo and every caps
lock press in the session toggles locally. This confirms the echo is injected.
The echo appears to be mstsc's own way to keep the local toggle state in step with the keys its
hook swallowed.
Observed with alpha-57522,5c1a74ca (2026.3.0.57522) on both machines, Windows 11 25h2.
Is your feature request related to a problem? Please describe
When caps lock is an NVDA modifier key and a full screen Remote Desktop session has focus,
quickly repeated caps lock presses toggle the local caps lock state unintentionally.
When the remote session also runs NVDA with caps lock as its modifier, the two machines end up
with different caps lock states.
The cause is that the RDP client swallows all physical keys in full screen mode and feeds caps
lock presses back into the client system as injected input.
NVDA processes that echo as its NVDA modifier, and two echoes within the multi-press timeout
trigger the double-press bypass, toggling caps lock on the client only.
A full analysis is under additional context below.
Add-ons cannot address this precisely, because no extension point exposes whether a key event
was injected:
inputCore.decide_handleRawKeyreceivesvkCode,scanCode,extendedandpressed, but not theinjectedflag the low level hook already has.Diagnosing the problem also required monkeypatching
winInputHook, for the same reason.Describe the solution you'd like
Pass
injectedin theinputCore.decide_handleRawKey.decide(...)calls inkeyboardHandler.internal_keyDownEventandinternal_keyUpEvent.This is backward compatible: extension point handlers only receive the keyword arguments they
declare, and an add-on handler can declare
injected=Noneto keep working on older NVDAversions.
With that flag, a remote desktop add-on can swallow the injected caps lock echo precisely.
Since
decide_handleRawKeyis the only extension point that fires before the "Handle keys fromother applications" check, this also covers users who have that setting disabled.
Describe alternatives you've considered
(msrdc, Citrix and others would need their own), and without an add-on that has a channel to
the server it only suppresses the spurious toggle. It might even make things worse, since the
echo is the client's only way to keep the local state in step with the session.
RDAccess currently implements. It works, but only
matches the default "Apply Windows key combinations: only when using the full screen"
configuration, and it cannot see at the gesture stage whether a key was injected.
Additional context
Reproduce the underlying problem by setting caps lock as an NVDA modifier key on both ends of a
full screen mstsc session and pressing caps lock+down arrow twice within about half a second:
the client toggles and announces caps lock, while the server does not change.
Both machines were instrumented with a wrapper around the low level keyboard hook, logging each
event's
LLKHF_INJECTEDflag, NVDA's trap or pass decision, and the toggle state. Observed:installs its own low level hook, which runs before NVDA's, swallows every key, and forwards
it to the server.
lock event mirroring the physical timing. Only caps lock is echoed; other keys are not.
nothing clears
lastNVDAModifier. An echo withinmultiPressTimeoutof the previous onetherefore triggers the double-press bypass in
keyboardHandler.internal_keyDownEvent: thekey passes to the OS and the local caps lock toggles.
modifier. The companion key clears its
lastNVDAModifier, so the server never toggles.lock press in the session toggles locally. This confirms the echo is injected.
The echo appears to be mstsc's own way to keep the local toggle state in step with the keys its
hook swallowed.
Observed with alpha-57522,5c1a74ca (2026.3.0.57522) on both machines, Windows 11 25h2.