Files
cthulhu/docs/tentacle-remote-capture.md
ak47 64f60fe4e8 Honor Num Lock during Remote keyboard capture
Forward numeric keypad identities with Num Lock enabled and preserve held-key pairing across lock changes. Suppress local lock feedback during remote capture so it cannot interrupt incoming speech. Add debug speech and capture-end diagnostics, expand private keypad checks, and repair the suspension probe source list.

Validated affected Go packages, focused remote speech race checks, the private X11 Remote input harness, and a reader build. Live physical keyboard, audible PowerShell speech, Braille, and disconnect-cause verification remain pending; the active reader session was left running.
2026-11-07 22:37:39 -05:00

110 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Remote controller keyboard capture IPC
Protocol 1.1 now carries controller keyboard capture between the reader and a
Remote Tentacle, and the Xorg command owner provides the exclusive capture
itself. Live physical-keyboard acceptance is pending.
## Wire contract
Child-to-host `remote.focus` asks core to enter or leave remote focus:
```json
{"lease":"0123456789abcdef0123456789abcdef","epoch":1,"sequence":3,"remote":true}
```
All four fields are required. It needs the current process session's live
controller lease with the separately approved `keyboard-capture` grant and
shares the strictly increasing operation sequence. The reply `{}` acknowledges
admission only. Without an attached reader capture owner it returns
owner-unavailable. A full four-request mailbox revokes the lease, so a lost
request to return to local focus can never leave capture active.
Host-to-child `remote.focus` events report the actual state, `focus` being
`local` or `remote` and `reason` one of `requested`, `escape`, `local` (reader
policy such as lock, handoff or reader UI) or `unavailable`. A child request
never proves that capture is physically active.
Host-to-child `remote.input` events carry one normalized, routed transition:
```json
{"code":38,"name":"a","pressed":true}
{"code":38,"pressed":true,"repeat":true}
{"code":38,"pressed":false}
```
`code` is the source Xorg keycode (8–255), used only to pair repeats and
releases. `name` is a canonical unshifted key name from
`input.ValidRemoteKeyName` and is present only on a fresh press; releases and
repeats omit it so a layout change cannot strand a key. A full event queue
fails the session rather than losing a release.
Local Num Lock selects numeric keypad names (`KP_0`–`KP_9`, `KP_Decimal`)
instead of navigation names. Shift remains a separate key transition. A keypad
press retains its original identity through repeats and release even if Num Lock
changes while it is held. Numeric `KP_0` does not act as the reader's Insert
modifier; keypad Insert with Num Lock off still does.
Reader code attaches the mailbox with `Session.EnableRemoteCapture` and
publishes with `PublishRemoteInput` and `PublishRemoteFocus`; both require the
live controller `keyboard-capture` lease. Go exposes `RemoteCapturedKey`,
`RemoteFocusRequest` and validators through `sdk/go/tentacle`; Python exposes
`valid_remote_captured_key` and `valid_remote_focus_request` and validates both
event kinds. Revocation drains pending focus work.
Go/Python SDK-process tests cover owner absence, admission and replay,
role/grant denial, invalid parameters, saturation revocation, event delivery
and rejection after revocation.
## Xorg capture owner
`CommandListener.BeginRemoteCapture` adds a third exclusive mode to the Xorg
command owner, next to internal reader UI and learn mode. It starts only
between chords, while command grabs are allowed and no other mode is active,
and takes `XGrabKeyboard` on the root window without changing input focus.
Each native transition passes through `RemoteRepeatNormalizer` and
`RemoteCaptureRouter`. Ordinary keys and Cthulhu modifier chords become
`remote.input` events; the router releases a forwarded prefix remotely before a
local chord. Keys outside the Remote vocabulary are consumed locally.
Only two things stay local. The core escape, F11 with no modifiers, ends capture
on the X thread without waiting for a Tentacle or the network. Shortcuts for
commands whose IDs start with the requesting Tentacle's `tentacle/<session>/`
prefix, as resolved from current bindings including remaps, also return the
keyboard here (reason `requested`) without running the command; running it
would race with the focus change. Pressed again under local focus they run. Lock, secure input, XTerm handoff, learn mode, OCR review,
a device hierarchy change, an inconsistent stream or a full 512-event queue end
capture with reason `local`.
Ending releases every forwarded key through the stream, newest first, then
sends the end marker and closes the stream. Keys still physically held keep the
grab until their releases arrive, so no release reaches an application that
never saw the press. While capture is active the XRecord listener's local key
echo, speech interruption and local lock-key announcements are suppressed.
The peer supplies its own lock feedback. Local lock tracking continues, and
announcements resume for subsequent changes after returning to local control.
Debug logs record capture-ending reasons and remote speech text handed to the
software speech backend, with spelling/language state and priority. Remote speech
text is omitted at ordinary log levels, as for other private reader output.
`start_remote_capture` attaches this owner for sessions installed with
`keyboard-capture`, from both local launch and managed startup. It enters
capture only for a current controller lease while the reader is unlocked, not
handed off and showing no reader UI. Revocation of the lease ends capture.
A private Xvfb test drives XTEST keys into the real owner with a focused `xev`
window as the local application: plain keys, Shift+Tab and a reader-modifier
chord are forwarded with correct names and pairing; the Remote shortcut
withdraws its prefix and ends capture without running; unsupported keys and F11
are consumed; and the
window receives none of the captured keys but does receive input afterwards.
Lock ends capture with a held modifier released remotely. Five repeated runs
passed. App tests with a synthetic owner and a real Go SDK child cover focus
reporting, key forwarding, escape and requested exit, revocation, policy
denial and grab failure.
Known limits: the escape is a fixed chord and is not remappable; a withdrawn
prefix reaches the remote as a modifier tap; XTEST input cannot establish
physical-keyboard behaviour; and Caps Lock as the Cthulhu modifier keeps its
local lock state but is forwarded as a key.