Skip to content

fix(windows): make Ctrl-C interrupt panes and multi-line paste arrive as one block - #17

Merged
whackur merged 2 commits into
code0xff:devfrom
whackur:fix/windows-pane-input
Aug 2, 2026
Merged

fix(windows): make Ctrl-C interrupt panes and multi-line paste arrive as one block#17
whackur merged 2 commits into
code0xff:devfrom
whackur:fix/windows-pane-input

Conversation

@whackur

@whackur whackur commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Two Windows-only defects in terminal panes. Both were reproduced, root-caused against real evidence, and verified running.

Ctrl-C never interrupted a running program

ping 8.8.8.8 in a pane could not be stopped. The input path was already correct — map_key leaves Ctrl-C unmapped, encode_key emits 0x03, send_input writes and flushes.

The cause is a console disposition: a process inherits its parent's ignore-Ctrl-C flag, and daemon::detach passes CREATE_NEW_PROCESS_GROUP, which sets that flag outright. Every ConPTY pane inherited it. conhost still delivered the 0x03 byte — an idle prompt abandoned its line, so it looked wired up — but raised no CTRL_C_EVENT.

Verified with a portable-pty probe: with the flag set, ping survives 0x03; after SetConsoleCtrlHandler(NULL, FALSE), the same byte produces ^C and kills it.

The clear lands in run_daemon, not the TUI: attach owns no hub and only forwards keystrokes over the socket, so the daemon is the ConPTY parent.

Multi-line paste split into one submission per line

crossterm's Windows event source produces only Key/Mouse/WindowBufferSize/Focus records — Event::Paste never fires, so EnableBracketedPaste is a no-op there and a paste arrives as one key press per character.

input::burst coalesces the burst into a synthetic Event::Paste, reusing the existing bracketed-paste path unchanged. Two details came out of instrumented runs rather than reasoning:

  • Enter must become \r, not \n. Terminals normalise paste line breaks to CR; \n is dropped by some readers, collapsing the lines.
  • The drain cannot use a zero-wait poll. The console feeds a paste in incrementally, so the queue reads momentarily empty and the paste is cut mid-word into many separate bracketed blocks. A 5 ms gap bridges it, far below human typing cadence.

The classifier is deliberately narrow — every event a plain unmodified character or Enter press, and either Enter plus another character or more than 16 characters — because a false positive submits typed keys as a block. Anything else falls back to per-event dispatch in arrival order.

Verification

cargo fmt --all --check, cargo build, cargo clippy --all-targets --all-features -- -D warnings, cargo test (1457 passed). 10 new tests cover the classification rule.

Confirmed by hand in a real pane: ping stops on Ctrl-C, and a 7-line paste arrives as a single block with no loss. Unix behaviour is untouched — the Unix seam is a no-op and the burst call site is #[cfg(windows)].

whackur added 2 commits August 2, 2026 15:11
…nes spawn

A process inherits its parent's ignore-Ctrl-C flag, and CREATE_NEW_PROCESS_GROUP
in daemon::detach sets it outright, so every ConPTY pane inherited it. conhost
still delivered the 0x03 byte — an idle prompt abandoned its line and looked
wired up — but raised no CTRL_C_EVENT, leaving a running program uninterruptible.
The Windows console has no paste input record, so crossterm never emits
Event::Paste there and a paste arrives as one key press per character — every
\r submitting a line. Coalesce the already-queued burst into a synthetic event
so the existing bracketed-paste path applies unchanged.
@whackur
whackur merged commit a6a93c4 into code0xff:dev Aug 2, 2026
6 checks passed
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.

1 participant