Skip to content

fix: inject text for control-socket send_key so keys reach the PTY - #112

Open
calebcarvalho wants to merge 2 commits into
am-will:mainfrom
calebcarvalho:fix/control-socket-send-key-text
Open

fix: inject text for control-socket send_key so keys reach the PTY#112
calebcarvalho wants to merge 2 commits into
am-will:mainfrom
calebcarvalho:fix/control-socket-send-key-text

Conversation

@calebcarvalho

Copy link
Copy Markdown

Summary

send-key over the control socket is silently ignored — no key (not even a
printable character, and critically not Return) reaches the shell. This makes
socket-driven automation unusable on Linux: you can build layouts and send
text into a pane, but you can never submit a command.

Root cause

TerminalHandle::send_key builds its ghostty_input_key_s via
translate_key_event, which hard-codes:

ghostty_input_key_s {
    ...
    text: ptr::null(),
    ...
}

Ghostty relies on the text field to write the character to the PTY. Because
send_key never populates it, ghostty_surface_key receives a key event with
no text and nothing is written to the shell. key_event_text can't help here
either — it deliberately returns None for control characters, so Return
(\r) would be dropped even if it were consulted.

Repro on main:

limux send     --surface <ref> "echo hi"   # text appears at the prompt
limux send-key --surface <ref> Return       # returns OK, but nothing executes
limux read-screen --surface <ref>           # command still sitting at the prompt, unexecuted

Fix

Populate press.text in send_key from a small helper that maps the common
control keys to their PTY bytes (Return/KP_Enter\r, Tab\t,
Escape\x1b, BackSpace\x7f) and falls back to key_event_text
for printable characters. Only the control-socket path is touched; interactive
GTK key handling is unchanged.

Test plan

  • limux send-key --surface <ref> Return on a bare bash prompt now submits
    the buffered command (verified end-to-end: the command executes and its
    output appears, followed by a fresh prompt).
  • Printable keys sent via the socket now reach the shell.
  • Interactive typing in a pane is unaffected (helper is only invoked from
    send_key).
  • cargo build --release is clean.

Notes

This is the same underlying issue as the send-key Return portion of #108
(which was closed unmerged); this PR is scoped to just the input fix and
extends it to the other common control keys.

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: rust/limux-host-linux/src/terminal.rs:183-211 still sends keycode: 0. Ghostty derives physical key only from keycode, so this fixes printable input and four hardcoded controls but leaves arrows, function keys, and physical keybindings unidentified. send-key Up, for example, still has neither text nor physical key and cannot produce its PTY sequence.

Map keyval to display keycode as #114 does, then verify Return plus one non-text key end to end. Socket-only scope and CString lifetime look good.

nonblocking: no regression coverage or CI result currently exercises non-text keys or terminal keyboard modes.

(Review assisted by gpt-5.6-sol)

tairea added a commit to tairea/limux that referenced this pull request Jul 13, 2026
Addresses @bvolpato's review. He is right, and the fix was incomplete.

A socket-injected key needs BOTH halves of the ghostty key event, and supplying
only one drops it silently:

  keycode only  ->  `limux send-key a` returns OK and writes nothing
  text only     ->  Enter, arrows, F-keys and ctrl-chords cannot be encoded

The original commit fixed the first half (keycode 0 meant ghostty saw an
unidentified physical key and dropped the event), which made Enter and arrows
work and closed the four-verb loop. But `translate_key_event` still left `text`
null, so ordinary printable input had nothing to write and vanished exactly as
before. It went unnoticed because orchestration uses `send` for text and
`send-key` only for Enter.

The codebase already says so, 550 lines above the bug, on the GTK key controller:

    // Send key events with the text field populated. Ghostty uses the
    // text field for actual character input and the keycode for bindings.

So: mirror the GTK path. Populate `press.text` from the existing `key_event_text`
helper, keeping the CString alive across `ghostty_surface_key`; leave the release
event textless, as the GTK controller does. The GTK path sources this text from
the input method -- a socket-injected key has no IM behind it, so derive it the
same way GTK's own fallback does.

`key_event_text` returns None for control characters, and that is load-bearing
rather than incidental: Enter and ctrl-chords must be *encoded* by ghostty from
key + mods, not written as literal bytes. Writing "\r" as text instead of letting
ghostty encode Return is how you break the kitty keyboard protocol.

This makes am-will#112 and this PR complementary rather than alternatives -- am-will#112 supplies
the text half, this supplies the physical-key half, and both are required.

Verified live on Wayland, all four classes at once, because fixing one by breaking
another is the obvious failure mode here:

  printable   `send-key e,c,h,o,space,h,i`  -> `echo hi` appears at the prompt
  Enter       `send-key enter`              -> it executes (once)
  arrows      `send-key up` then enter      -> history recalled; it executes twice
  ctrl-chord  `send-key '<ctrl>c'`          -> ^C interrupts a running `sleep 60`

Regression test pins the contract for all three key classes.

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Current quality check is green, but current head still has two functional blockers:

  • terminal.rs:183-198 sends keycode: 0. Ghostty resolves physical key only from keycode, so arrows, function keys, and physical bindings remain unidentified even though printable text and four hardcoded controls work.
  • main.rs:763-765 uses shell echo for arbitrary --shell-safe payloads. dash and zsh interpret backslash escapes; payload containing \c is truncated and \t is changed. Use quoted printf '%s\n' or equivalent byte-preserving command.

Latest shell-safe tests only assert generated source string, so they miss target-shell interpretation. Please map keyval to display keycode and test shell output with escape-bearing payload.

(assisted by gpt-5.6-sol)

bvolpato pushed a commit to bvolpato/limux that referenced this pull request Aug 1, 2026
Addresses @bvolpato's review. He is right, and the fix was incomplete.

A socket-injected key needs BOTH halves of the ghostty key event, and supplying
only one drops it silently:

  keycode only  ->  `limux send-key a` returns OK and writes nothing
  text only     ->  Enter, arrows, F-keys and ctrl-chords cannot be encoded

The original commit fixed the first half (keycode 0 meant ghostty saw an
unidentified physical key and dropped the event), which made Enter and arrows
work and closed the four-verb loop. But `translate_key_event` still left `text`
null, so ordinary printable input had nothing to write and vanished exactly as
before. It went unnoticed because orchestration uses `send` for text and
`send-key` only for Enter.

The codebase already says so, 550 lines above the bug, on the GTK key controller:

    // Send key events with the text field populated. Ghostty uses the
    // text field for actual character input and the keycode for bindings.

So: mirror the GTK path. Populate `press.text` from the existing `key_event_text`
helper, keeping the CString alive across `ghostty_surface_key`; leave the release
event textless, as the GTK controller does. The GTK path sources this text from
the input method -- a socket-injected key has no IM behind it, so derive it the
same way GTK's own fallback does.

`key_event_text` returns None for control characters, and that is load-bearing
rather than incidental: Enter and ctrl-chords must be *encoded* by ghostty from
key + mods, not written as literal bytes. Writing "\r" as text instead of letting
ghostty encode Return is how you break the kitty keyboard protocol.

This makes am-will#112 and this PR complementary rather than alternatives -- am-will#112 supplies
the text half, this supplies the physical-key half, and both are required.

Verified live on Wayland, all four classes at once, because fixing one by breaking
another is the obvious failure mode here:

  printable   `send-key e,c,h,o,space,h,i`  -> `echo hi` appears at the prompt
  Enter       `send-key enter`              -> it executes (once)
  arrows      `send-key up` then enter      -> history recalled; it executes twice
  ctrl-chord  `send-key '<ctrl>c'`          -> ^C interrupts a running `sleep 60`

Regression test pins the contract for all three key classes.
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.

3 participants