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.
110 lines
5.8 KiB
Markdown
110 lines
5.8 KiB
Markdown
# 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.
|