Skip to content

Pass the injected flag to the decide_handleRawKey extension point #20714

Description

@LeonarddeR

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Addon/APIChanges to NVDA's API for addons to support addon development.audience/nvda-devPR or issue is relevant to NVDA / Add-on developersp5https://github.com/nvaccess/nvda/blob/master/projectDocs/issues/triage.md#prioritytriagedHas been triaged, issue is waiting for implementation.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions