Skip to content

Use NVDA 2026.3's injected flag for the client caps lock veto - #81

Open
LeonarddeR wants to merge 4 commits into
mainfrom
capsLockRawKeyVeto
Open

Use NVDA 2026.3's injected flag for the client caps lock veto#81
LeonarddeR wants to merge 4 commits into
mainfrom
capsLockRawKeyVeto

Conversation

@LeonarddeR

@LeonarddeR LeonarddeR commented Aug 27, 2026

Copy link
Copy Markdown
Owner

What exists

RDAccess keeps the caps lock key in sync between the client and the server. When a full screen remote desktop session captures the keyboard, the remote desktop client feeds every caps lock press back into the client system as a software-generated key event. NVDA on the client treats that fed back press as its own modifier key, and a quick second press then toggles caps lock on the client only.

Since version 2.0.2, the client swallows the caps lock gesture that NVDA is about to pass to the operating system (_vetoCapsLockToggle, registered on inputCore.decide_executeGesture), and the server reports its real caps lock state to the client, which applies it as soon as the session loses focus.

NVDA's raw keyboard extension point did not tell the client whether a key event was generated by software. The client therefore guessed whether the remote desktop client was capturing the keyboard from the shape of the foreground window: it only swallowed presses while that window covered its whole monitor (windowHelpers.isForegroundWindowFullScreen). Two gaps followed from that guess. Sessions that are not full screen but apply Windows key combinations on the remote computer still went out of sync. And with the NVDA setting "Handle keys from other applications" disabled, NVDA ignores software-generated keys before the gesture stage, so the veto never ran.

What is needed

NVDA 2026.3 passes the injected flag to handlers of inputCore.decide_handleRawKey (nvaccess/nvda#20748). That handler runs before NVDA looks at the "Handle keys from other applications" setting and before it turns the key into a gesture. A fed back caps lock press is always a software-generated event; a real press never is. The client can now tell them apart per key event instead of guessing from the window geometry.

What changes

  • The client swallows every software-generated caps lock key event, press and release, while a remote desktop client process has focus, caps lock is configured as an NVDA modifier key and the synchronization setting is on. Key events NVDA injects itself, such as the mirrored state and emulated caps lock gestures, still pass through. This is the plugin method _vetoInjectedCapsLock, registered on inputCore.decide_handleRawKey; it replaces _vetoCapsLockToggle and the full screen check.
  • The client registers that handler on NVDA 2026.3 and later only, and applies the state mirrored from the server on those versions only, so the client side of the synchronization is either fully available or not at all. The flag lives in lib.nvdaCompat as CAPS_LOCK_SYNC_SUPPORTED, sharing the version comparison the braille compatibility code already makes. The server side does not depend on the new NVDA version and keeps working on every supported version, so a client on NVDA 2026.3 gets the full feature against an older server.
  • windowHelpers is removed. Its shell window helper moves into the named pipe code, its only user.
  • The add-on version becomes 2.0.3. changelog.md now holds the 2.0.3 entry only; the readme gains sections for 2.0.2 and 2.0.3, and its description of the setting no longer mentions full screen sessions or the "Handle keys from other applications" limitation. It now states that the client side needs NVDA 2026.3 and that caps lock presses sent by other software while a session window has focus are suppressed as well.

Tests

The existing protocol tests for the caps lock attribute are unchanged. The new handler is NVDA keyboard hook glue that the unit test harness cannot import; it is verified manually against a remote session, like the rest of the plugin.

Known consequence

While a remote desktop session window has focus, the client now also swallows caps lock presses that other software sends, for example a leader in NVDA Remote Access. Turning the synchronization setting off restores the previous behavior.

Assisted by Claude

https://claude.ai/code/session_01TvK8A5MrcwxdM5Y6aCdWLc

LeonarddeR and others added 2 commits August 27, 2026 08:22
The client now swallows software-generated caps lock key events through
inputCore.decide_handleRawKey, which NVDA 2026.3 supplies with the injected
flag, instead of vetoing the bypassed caps lock gesture behind a full screen
window heuristic. The client side of the synchronization requires NVDA
2026.3; the server side keeps working on every supported version. Bump the
add-on version to 2.0.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvK8A5MrcwxdM5Y6aCdWLc
NVDA 2026.3 builds from before the injected flag was added call raw key
handlers without it. Default the parameter to None so the veto degrades to
pass-through there instead of raising on every key event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvK8A5MrcwxdM5Y6aCdWLc
Copilot AI lite review requested due to automatic review settings August 27, 2026 06:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new raw-key handler’s decide signature should explicitly accept the raw-key kwargs NVDA/Decider may pass (e.g., scanCode, pressed) to avoid runtime incompatibility and align with the new test harness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This pull request updates RDAccess’s client-side Caps Lock synchronization to rely on NVDA 2026.3’s new raw-key injected flag, allowing the add-on to reliably suppress remote-desktop “fed back” Caps Lock events without guessing based on fullscreen window geometry.

Changes:

  • Add InjectedCapsLockVeto and register it on inputCore.decide_handleRawKey (NVDA 2026.3+) to swallow software-injected Caps Lock events from focused remote desktop clients.
  • Introduce nvdaCompat.CAPS_LOCK_SYNC_SUPPORTED and gate client-side Caps Lock synchronization (including applying mirrored state) on NVDA 2026.3+.
  • Update unit tests and stubs for the new gating and veto behavior; refresh documentation and bump add-on version to 2.0.3.
File summaries
File Description
tests/test_nvdaCompat.py Adds unit tests for the NVDA version gate controlling Caps Lock sync support.
tests/test_capsLockVeto.py Adds unit tests for the raw-key injected Caps Lock veto behavior.
tests/_stubs.py Extends NVDA runtime stubs to include minimal winUser/keyboardHandler symbols for the veto tests and nvdaCompat reload paths.
readme.md Documents 2.0.3 and updates the Caps Lock sync setting description and NVDA 2026.3 client requirement.
changelog.md Updates the changelog entry to reflect the 2.0.3 Caps Lock sync change.
buildVars.py Bumps add-on version to 2.0.3.
addon/lib/windowHelpers.py Removes fullscreen detection helper, leaving only the shell window process helper.
addon/lib/nvdaCompat.py Adds CAPS_LOCK_SYNC_SUPPORTED and updates module docstring to include the new gate.
addon/lib/capsLock.py Introduces InjectedCapsLockVeto used to swallow injected Caps Lock raw key events.
addon/globalPlugins/rdAccess/handlers/_remoteHandler.py Gates application of mirrored Caps Lock state on CAPS_LOCK_SYNC_SUPPORTED.
addon/globalPlugins/rdAccess/init.py Registers/unregisters the raw-key veto on NVDA 2026.3+ and removes the gesture-level veto/fullscreen check path.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread addon/lib/capsLock.py Outdated
self._isEnabled = isEnabled
self._remoteProcessHasFocus = remoteProcessHasFocus

def decide(self, vkCode: int, extended: bool, injected: bool | None = None) -> bool:
LeonarddeR and others added 2 commits August 27, 2026 08:49
Fold the veto back into RDGlobalPlugin next to its server-side counterpart,
registered as a bound method on decide_handleRawKey, instead of a lib class
that took its inputs as callables. Drop the reload-based version test and the
stub scaffolding it needed, and compute the 2026.3 version check once in
nvdaCompat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvK8A5MrcwxdM5Y6aCdWLc
…tion

The shell window helper moves into namedPipe, its only user, so the module
that held the deleted full screen check goes away.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TvK8A5MrcwxdM5Y6aCdWLc
@LeonarddeR
LeonarddeR enabled auto-merge (squash) August 27, 2026 07:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants