fix: capture held prefix command keys via kitty release events#74
Merged
Conversation
bfe4d4f to
ca69cab
Compare
In `boo ui`, running a `C-a` prefix command while still holding the command key leaked the key's auto-repeats into the focused session: `C-a c` (create) printed a stray `c` in the new terminal. In legacy plain-byte encoding an auto-repeat is byte-for-byte identical to fresh typing, so it cannot be told apart by content. The one deterministic signal that a key is still held is a key release event, which the kitty keyboard protocol provides. boo forces the report-events flags on the real terminal for the brief windows where a held command key would otherwise leak: - boo ui: while the C-a prefix is engaged, force report-events so the command key's auto-repeats (event 2) and release (event 3) arrive as CSI-u events. The parser swallows them, plus modifier-key chord noise, until the key is released, then the flags revert. Fixes C-a c with no delay. - boo attach and boo ui quit: the post-detach input drain re-enables report-events and ends the instant the triggering key's release arrives instead of waiting out the timed guard, which stays as the fallback. Terminals without the kitty protocol ignore the forced flags and keep the prior timed-drain behavior; the latch self-heals when no release events can arrive. Restoring the terminal after the drain no longer uses TCSAFLUSH. On Darwin its output-drain half blocks until the PTY master has consumed the writes boo just made, so a peer that has briefly stopped reading (a detach test between reads, a stalled remote link) wedges the restore, and the input-flush half then discards input typed afterwards. boo now discards straggler input non-blockingly and applies the saved mode with TCSANOW. Tests: parser swallow-latch and ReleaseScan unit tests, plus a PTY integration test that arming forces report-events and the release reverts it. The pre-existing held-key detach tests exercise the restore path.
ca69cab to
3c5e65a
Compare
Merged
BenLocal
added a commit
to BenLocal/boo
that referenced
this pull request
Jun 18, 2026
Brings in upstream coder#73 (boo ui sessions created at the viewport size), coder#74 (capture held prefix keys via kitty release events), coder#75 (scrollback replay on attach), and the v0.5.21/v0.5.22 releases. Conflict resolution: - main.zig / daemon.zig: union the createSession / runDaemon / Options params — self's state_dir/cwd/max_scrollback (restore + config) plus main's rows/cols (viewport-size fix). restoreOne passes null rows/cols. - scrollback replay (ui.zig, daemon.zig, protocol.zig, client.zig): self and main implemented the same feature two ways. Kept main's design (a separate `.ui` marker message before a SizePayload attach) and dropped self's redundant AttachPayload.ui flag, so client and daemon agree on the wire. - window.zig: kept self's added alt-screen historyReplay test. Verified: zig fmt, zig build, unit tests, and PTY integration tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In
boo ui, running aC-aprefix command while still holding the command key leaks the key's auto-repeats into the focused session.C-a c(create) prints a straycin the new terminal. In legacy plain-byte encoding an auto-repeat is byte-for-byte identical to fresh typing, so it can't be told apart by content: the create path had no drain at all, while detach (C-a d, in bothboo uiandboo attach) papered over the same race with a timed input drain (300-800ms guards, 1500ms cap).Approach
The one signal that reliably separates a held key's tail from new typing is a key release event, which the kitty keyboard protocol provides (
report event types+report all keys). This depends on terminal capability, not the focused app's mode, so boo can opt the terminal into it for the brief windows where a held command key would otherwise leak.boo ui(src/ui.zig): while the prefix is engaged, force kitty report-events on the real terminal so the command key's auto-repeats (event 2) and release (event 3) arrive as explicit CSI-u events. The parser swallows them (and modifier-key chord noise) until the key is released, then the flags revert. FixesC-a cdeterministically with no delay.boo attach/boo uiquit (src/client.zig): the post-detach drain re-enables report-events and ends the instant the triggering key's release arrives, instead of waiting out the timed guard. The timed guard stays as the fallback.Terminals without the kitty protocol ignore the forced flags and fall back to the existing legacy drain, which is the best achievable without release events.
Testing
src/ui.zig) and theReleaseScandrain detector (src/client.zig).\x1b[=11;1u; the command key's release reverts it.zig fmt --check,zig build test(127 tests),zig build test-integration(70 tests), andzig build test-all -Doptimize=ReleaseSafe(197 tests).Design notes and decision log
Why release events. In legacy/modifyOtherKeys encoding a held key's auto-repeat is identical to fresh typing, so the old code could only guess with a timer. The kitty protocol's explicit press/repeat/release events are the only deterministic way to know the key is still down. So the fast path is driven by the release event, and the timer survives only as a fallback.
Terminal capability, not app mode. boo forces the flags itself for the brief prefix/teardown window, so the deterministic path works even for apps that never used kitty (e.g. a plain shell or
cat). No capability handshake is needed: the forced flags are a harmless no-op on terminals that don't implement the protocol.Why
report all keys(flag 8). Unmodified command keys likec/dare delivered as raw bytes under plain disambiguate mode, so event types alone wouldn't tag their repeats. Flag 8 makes them CSI-u so the latch can act.Graceful degradation / safety.
C-a C-dchord's Ctrl release does not end the drain whiledis still held.Restoring the terminal without TCSAFLUSH (macOS). The post-detach restore drains held-key input, then hands the tty back. It used
tcsetattr(TCSAFLUSH), but on Darwin the output-drain half ofTCSAFLUSHblocks until the PTY master has consumed boo's writes. When the peer momentarily stops reading (exactly what the held-key detach tests do between reads, and what a stalled remote link does), the restore wedges insidetcsetattr; when it later unblocks, the input-flush half discards the input typed in the meantime, so the next reader never sees it. boo now discards straggler input non-blockingly (zero-timeout poll/read in the still-raw mode) and applies the saved mode withTCSANOW, which never waits on output. Linux'stcsetattrdoes not wait on PTY output, which is why this only surfaced on macOS CI.Known limitations (documented, not fixed here).
C-a ccan still cosmetically leak a repeatedc, and detach still relies on the timed guard. This is unavoidable without release events.Out of scope / follow-up. The daemon-side
keys.Parser(used byboo attach) could grow the same release-swallow for non-detach commands (C-a lredraw,C-a aliteral) held over a kitty terminal; it needs the daemon to push report-events to the client and was left out to keep this change focused. Detach leaks overboo attachare already handled by the client drain.This PR was generated by Coder Agents on behalf of @kylecarbs.