fix: ESC permanently blocked after Ctrl+letter injection corrupts ConPTY VT parser state#329
Conversation
…Enter/ESC/Ctrl - Replace send_modified_enter_event calls with send_modified_key_event(pid, '\r', ...) - Switch Ctrl+letter injection from Win32 VT escape sequences to WriteConsoleInputW to avoid corrupting ConPTY's VT parser (ESC sequences left parser buffering \x1b) - Fix char_to_vk to handle '\x1b' and '\r' explicitly since VkKeyScanW returns -1 for non-printable chars; reuse char_to_vk/vk_to_scan in send_modified_key_event - Add tests for char_to_vk covering ESC, carriage return, and alphabetic chars
…ter injection (#329) Ctrl+letter keys were injected by writing Win32 input-mode VT escape sequences (ESC [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _) to the ConPTY pipe. These sequences start with 0x1B and corrupt ConPTY's VT parser state, permanently breaking ESC delivery to applications like Neovim. Replace with a two-channel approach: 1. Write the raw C0 control byte (ch & 0x1F) to the ConPTY pipe 2. Inject a KEY_EVENT via WriteConsoleInputW for PSReadLine compatibility Also consolidates send_modified_enter_event into send_modified_key_event, and fixes char_to_vk to handle ESC (0x1B) and Enter (0x0D) explicitly. Authored-by: LizardLiang <LizardLiang@users.noreply.github.com>
|
Hey @LizardLiang, great catch and a really well-written PR. The root cause analysis is spot on. The Win32 VT input mode sequences ( I verified this thoroughly:
Merged via squash to master in commit 0af970f. Thanks for the contribution! |
|
Hi @psmux , this PR inadvertently introduced a double delivery bug for Ctrl+letter keys on Windows Apologies for the inconvenience! I've opened a follow-up PR that resolves these issues. Would you mind |
Problem
After pressing
Ctrl+W(or anyCtrl+letter) inside a pane running Neovim, ESC stops being delivered to the application entirely. The pane stays alive and accepts other keystrokes, but the user is stuck and cannot leave insert mode or any other ESC-dependent state.Reproduce
:FloatermNew)<C-w>to switch windows<Esc>— it is no longer delivered to NeovimRoot cause
Ctrl+letter keys were injected by writing Win32 input-mode VT escape sequences (format:
ESC [ Vk ; Sc ; Uc ; Kd ; Cs ; Rc _) directly into the ConPTY pipe. These sequences start with\x1b. When they reach a pane running Neovim, ESC handling breaks: subsequent bare\x1bbytes from real ESC keypresses are no longer delivered correctly.The same Win32 VT sequence path was used in both the live-keypress handler and the
send-keys C-xcommand.Investigation
Tracing the ESC delivery failure led to the Ctrl+letter injection path. Every Ctrl+letter injected a Win32 input-mode VT escape sequence — a string starting with
\x1b— directly into the ConPTY pipe. ConPTY's VT parser consumed the sequence, but the parser was left in a state where a bare\x1barriving immediately after was treated as the beginning of another escape sequence rather than a standalone ESC keypress. The next real ESC the user pressed fed into that buffered state and was swallowed.Fix
Ctrl+letter injection now uses
WriteConsoleInputWviasend_modified_key_event, which writes directly into the child's console input buffer without touching the ConPTY VT pipe. This delivers the correct virtual key code andLEFT_CTRL_PRESSEDflag to apps that read fromCONIN$(such as PSReadLine), while leaving the VT parser state untouched. Mirroring the existing Alt+key pattern: trysend_modified_key_eventfirst; write the raw byte only if injection fails (no pid, orAttachConsolereturned an error).'c'is excluded from theWriteConsoleInputWbranch and always falls through to the raw byte path.WriteConsoleInputWputs aKEY_EVENTinto the input queue — it does not triggerCTRL_C_EVENT. Process termination for apps likepingdepends onCTRL_C_EVENT, which only ConPTY can generate, and only from the raw byte path (whenENABLE_PROCESSED_INPUTis set on the child's console). There is no single API that delivers both aKEY_EVENTand aCTRL_C_EVENTin one call; the two are separate Windows console mechanisms. Using the raw byte for'c'lets ConPTY decide:CTRL_C_EVENTwhenENABLE_PROCESSED_INPUTis on (shells, CLI tools),KEY_EVENTwhen it is off (Neovim raw mode) — the same adaptive behaviour as a real keyboard.char_to_vknow explicitly maps'\x1b'→0x1B(VK_ESCAPE) and'\r'→0x0D(VK_RETURN).VkKeyScanWreturns-1for non-printable characters; without these arms the old code produced VK code0, generatingKEY_EVENTs that applications discard.send_modified_enter_eventwas removed. All key injection — Enter, ESC, Ctrl+letter — now flows through the singlesend_modified_key_event(pid, ch, ctrl, alt, shift)entry point.GenerateConsoleCtrlEventis deliberately absent fromsend_modified_key_event. FiringGenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)while attached to a child's console sends the signal to that process's entire group. When the pane is running Neovim, that group includes Neovim itself — observed to kill Neovim instead of the foreground process inside a nested terminal (e.g. Floaterm). Signal delivery viaGenerateConsoleCtrlEventis the responsibility ofsend_ctrl_c_event, which is invoked only from thesend-keys C-ccode path.Test plan
cargo test char_to_vk—escape_returns_vk_escape,carriage_return_returns_vk_return,alphabetic_lowercase_maps_to_vk,alphabetic_uppercase_same_as_lowercaseping -tstops on Ctrl+CChanges
src/input.rssend_modified_key_eventfor Ctrl+letter (live-keypress andsend-keyspaths); exclude'c'fromWriteConsoleInputWbranch so Ctrl+C retains raw byte delivery; replacesend_modified_enter_eventcalls withsend_modified_key_event(pid, '\r', ...)src/platform.rs'\x1b'→0x1Band'\r'→0x0Dexplicit arms inchar_to_vk; removesend_modified_enter_event(merged intosend_modified_key_event)tests-rs/test_char_to_vk.rsescape_returns_vk_escape,carriage_return_returns_vk_return,alphabetic_lowercase_maps_to_vk,alphabetic_uppercase_same_as_lowercase