Skip to content

feat: repeat navigation keys through the repeat envelope - #680

Merged
enaboapps merged 7 commits into
mainfrom
key-repeat-protocol-677
Sep 8, 2026
Merged

enaboapps merged 7 commits into
mainfrom
key-repeat-protocol-677

Conversation

@enaboapps

Copy link
Copy Markdown
Contributor

@-

OwenMcGirr and others added 6 commits September 7, 2026 20:13
Adds held/repeating keyboard input so a remote can repeat the arrow keys,
Tab, Backspace, Delete, Page Up and Page Down, mirroring the existing
mouse move and scroll repeat.

Key repeat rides the existing `mouse.repeat.start` nested command envelope
rather than introducing new command names. The desktop's command
vocabulary is hand-maintained across five parallel lists, so a new
namespace would need entries in all of them plus a permanent alias for
existing clients; widening the envelope needs none and reuses the repeat
generation, stop and cleanup machinery unchanged. A new `keyRepeat`
capability block gates the feature, so old and new peers on either side
degrade to single key presses.

Each tick is a discrete press and release rather than a held key, so a
repeat loop that dies for any reason cannot strand a key down. The one
remaining gap, a press that succeeds followed by a failed release, is
recorded in `pending_key_releases` and retried by `release_all` - the same
deterministic cleanup path used for modifiers.

Repeatable keys deliberately exclude printable characters and Space, which
would flood text, modifiers, which already latch through
`keyboard.modifierDown`, and Enter, which would re-submit on every tick.
Modifiers latched before a repeat stay held across ticks so Shift with a
repeating arrow extends a selection.

Closes #677

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ding

Addresses independent review findings on the key repeat change.

A repeat tick whose key-up fails leaves the key physically held, and the
OS's own auto-repeat then keeps firing it into the focused application.
Recovery previously waited for release_all on a terminal path, so a stuck
Backspace could delete continuously until the phone disconnected. Every
repeat stop path on both runtimes now retries the pending release.

Key repeats inject keystrokes but bypass DesktopInput::execute, which
refuses keyboard input during Switch Forwarding. The repeat start path now
enforces that guard itself rather than offering a way around it.

Toggling "Repeat held keys" off mid-repeat also stops the active repeat,
so the runtime loop ends deliberately instead of surfacing a red
"Key repeat stopped" error on its next tick.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on change

Addresses the second independent review of this branch.

The initial-tap failure arm on both runtimes dropped the repeat straight
through the controller, bypassing the runtime helper that retries a failed
key release. Every later cleanup path is gated on a repeat actually being
stopped, so that key stayed held. Both arms now release it.

save_settings tested the incoming values rather than a change, so with key
repeat persisted off any unrelated settings save cancelled an in-flight
mouse repeat. It now compares against the previous values, matching the
dwell block above it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses the third independent review of this branch.

The stop helpers only retried a pending key release when a repeat had
actually been stopped. If a tick's key-up failed and the immediate retry
failed too, the key stayed in pending_key_releases with no active repeat,
so no later stop would ever free it and the OS kept auto-repeating it into
the focused application until disconnect.

release_repeat_keys already returns immediately when nothing is pending, so
calling it unconditionally costs nothing on the common path and removes the
gap on both runtimes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The defensive RepeatCommand::Key arm in BeginRepeat returned after
control_active, drag_active and repeat_generation had already been set and
without clearing feedback, so if it ever became reachable a later
re-render could paint stale pointer feedback. The check now runs before any
state is touched, alongside the disabled-overlay guard.

Unreachable today, since both runtimes hide the overlay for key repeats
rather than beginning one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Key repeat start reported "Accessibility permission is required before the
pointer can move." even though it injects keystrokes, while the branches
either side of it carefully distinguish key from mouse wording.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@enaboapps

Copy link
Copy Markdown
Contributor Author

@-

The comment read as though the repeat path enforced Switch Forwarding for
every repeat kind. Pointer repeats have always been allowed during a
session; only the new keyboard injection is guarded here.
@enaboapps
enaboapps marked this pull request as ready for review September 7, 2026 23:02
@enaboapps
enaboapps merged commit bae167a into main Sep 8, 2026
6 checks passed
@enaboapps
enaboapps deleted the key-repeat-protocol-677 branch September 22, 2026 09:14
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