Skip to content

fix(input): prevent double Ctrl+letter delivery; use raw byte only for Ctrl+C#333

Closed
LizardLiang wants to merge 1 commit into
psmux:masterfrom
LizardLiang:fix/esc-cannot-pass-to-neovim-after-using-CtrlW
Closed

fix(input): prevent double Ctrl+letter delivery; use raw byte only for Ctrl+C#333
LizardLiang wants to merge 1 commit into
psmux:masterfrom
LizardLiang:fix/esc-cannot-pass-to-neovim-after-using-CtrlW

Conversation

@LizardLiang

Copy link
Copy Markdown
Contributor

Hotfix for two regressions introduced by #329.

Regression 1 — Ctrl+letter triggers twice

Problem

Any Ctrl+letter keypress (e.g. <C-w> in Neovim) fires twice — one press causes two actions.

Root cause

#329 switched Ctrl+letter injection from Win32 VT sequences to two simultaneous delivery paths: writing the raw C0 control byte to the ConPTY pipe AND injecting a KEY_EVENT via WriteConsoleInputW. Both paths land in the child's console input buffer. ConPTY converts the raw byte into a KEY_EVENT, and WriteConsoleInputW injects a second one — the child sees the keypress twice.

Fix

Mirror the existing Alt+key pattern: try WriteConsoleInputW first; write the raw byte only if injection fails. Exactly one delivery fires per keypress.


Regression 2 — Ctrl+C cannot stop processes

Problem

Pressing <C-c> no longer interrupts running processes such as ping.

Root cause

WriteConsoleInputW puts a KEY_EVENT record into the input queue. It does not trigger CTRL_C_EVENT. Process termination for CLI tools depends on CTRL_C_EVENT, which only ConPTY generates, and only from the raw byte path when ENABLE_PROCESSED_INPUT is set on the child's console. With both fixes from Regression 1 applied, 'c' now also goes through WriteConsoleInputW — so CTRL_C_EVENT is never generated and ping cannot be interrupted.

Fix

'c' is excluded from the WriteConsoleInputW branch and always falls through to the raw byte path. There is no single Windows API that delivers both a KEY_EVENT and a CTRL_C_EVENT; they are separate console mechanisms. The raw byte delegates the decision to ConPTY: CTRL_C_EVENT when ENABLE_PROCESSED_INPUT is on (shells, CLI tools), KEY_EVENT when it is off (Neovim raw mode) — the same adaptive behaviour as a real keyboard.


Test plan

  • <C-w> in Neovim triggers exactly once per keypress
  • ESC works normally after any Ctrl+letter keypress in Neovim
  • ping -t stops on Ctrl+C
  • PSReadLine Ctrl+letter shortcuts work (Ctrl+E, Ctrl+W)
  • PSReadLine Ctrl+C cancels the current input line

Changes

File Change
src/input.rs Use WriteConsoleInputW-first, raw-byte-fallback pattern for all Ctrl+letters; exclude 'c' from WriteConsoleInputW branch

…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes Windows input regressions introduced by #329 by ensuring Ctrl+letter keystrokes are delivered exactly once, while preserving Ctrl+C’s ability to generate CTRL_C_EVENT for interrupting console processes.

Changes:

  • Switch Ctrl+letter handling to “WriteConsoleInputW first, raw-byte fallback” to prevent double-delivery.
  • Special-case Ctrl+C to always use the raw control byte path so ConPTY can generate CTRL_C_EVENT.
  • Apply the same delivery strategy to both live key forwarding (including sync-input fanout) and the send-keys C-x path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ryannewcomer

Copy link
Copy Markdown

hey any update on this issue? i just got psmux and is having the same issue. it seems the @psmux haven't fix it yet. should i just build this from source with @LizardLiang folk? many thanks

@ryannewcomer

Copy link
Copy Markdown

hey hum i got the lastest version which has try to fix this issue (#329 ) but it's stil broken right now.

@LizardLiang

Copy link
Copy Markdown
Contributor Author

hey hum i got the lastest version which has try to fix this issue (#329 ) but it's stil broken right now.

I'm no longer able to reproduce this on this branch. Could you share the steps you used to trigger it? That'll help me confirm whether it's actually fixed or just not surfacing in my setup.

@ryannewcomer

Copy link
Copy Markdown

hey @LizardLiang :

i get psmux from winget (verison 3.3.5), make a .psmux.conf like this:

set -g prefix C-f

set -g default-shell pwsh

# plugisn
set -g @plugin 'psmux-plugins/ppm'
set -g @plugin 'psmux-plugins/psmux-sensible'
set -g @plugin 'psmux-plugins/psmux-prefix-highlight'
set -g @plugin 'psmux-plugins/psmux-battery'
set -g @plugin 'psmux-plugins/psmux-theme-nord'


run '~/.psmux/plugins/ppm/ppm.ps1'

then restart and install plugins. everything is normal.

i then start a new session and re-name it, open 2 windows, rename the first one and open nvim on the second one and start editing a file. then as im trying to escape by to normal mode, it just stuck there.

i can still see the plugin is working normally and i can still switch windows. the first window also preforms well. I have also try to open a new session and edit the same file in neovim and as soon as i press esc, it just suck there, no response to that window, but i can still use prefix and all other key blind.

what's even wired is that when i open neovim on test file, i can type and esc normally. but when i try to improt a model (this is python) it just stuck. it might have to do with my neovim config, which you can check here

I am using windows terminal 1.24.11321.0 on pwsh.exe.

many thanks

@ryannewcomer

Copy link
Copy Markdown

also when i open a new pwsh.exe, this error pop up:

Screenshot 2026-06-02 135351

@LizardLiang

Copy link
Copy Markdown
Contributor Author

hey @LizardLiang :

i get psmux from winget (verison 3.3.5), make a .psmux.conf like this:

set -g prefix C-f

set -g default-shell pwsh

# plugisn
set -g @plugin 'psmux-plugins/ppm'
set -g @plugin 'psmux-plugins/psmux-sensible'
set -g @plugin 'psmux-plugins/psmux-prefix-highlight'
set -g @plugin 'psmux-plugins/psmux-battery'
set -g @plugin 'psmux-plugins/psmux-theme-nord'


run '~/.psmux/plugins/ppm/ppm.ps1'

then restart and install plugins. everything is normal.

i then start a new session and re-name it, open 2 windows, rename the first one and open nvim on the second one and start editing a file. then as im trying to escape by to normal mode, it just stuck there.

i can still see the plugin is working normally and i can still switch windows. the first window also preforms well. I have also try to open a new session and edit the same file in neovim and as soon as i press esc, it just suck there, no response to that window, but i can still use prefix and all other key blind.

what's even wired is that when i open neovim on test file, i can type and esc normally. but when i try to improt a model (this is python) it just stuck. it might have to do with my neovim config, which you can check here

I am using windows terminal 1.24.11321.0 on pwsh.exe.

many thanks

Just to make sure we're on the same page — have you tried building from source with the commit from #333? I've gone through your steps and used your psmux config, but the issue still isn't reproducing for me.

@ryannewcomer

Copy link
Copy Markdown

hi

i have build psmux from source with the latest commit and it seems to work. however, when i open a new window with prefix + c, i cannot type on it. the key blind still work tho.

this is the only issue ive counter after building psmux

@psmux

psmux commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Hey @LizardLiang and @ryannewcomer, thanks both for the diagnosis and the back-and-forth on this thread.

Confirming what @LizardLiang already saw on his side: master is fine on both fronts. Commit 6bd8ec4 (#338) reshaped the Ctrl-letter path on master to branch on Ctrl+C — Ctrl+C goes through send_ctrl_c_event (so ConPTY raises CTRL_C_EVENT) and every other Ctrl+letter goes through send_modified_key_event. I just re-ran a fresh repro on master and got the right behaviour:

  • ping /t 127.0.0.1 + Ctrl+C interrupts cleanly (10/10 in tests/test_issue346_ctrlc_ping.ps1)
  • hello world + Ctrl+W in pwsh + PSReadLine deletes exactly one word, leaves hello (tests/test_pr333_ctrlw_repro.ps1, just landed)

The reason ryannewcomer hit it on 3.3.5 is that the winget build is from the v3.3.5 tag which is BEFORE 6bd8ec4 landed; the next release will pick it up. Closing this PR since the bug is already gone on master, with credit to your diagnosis, @LizardLiang — your write-up of the two regressions was what made it easy to verify.

@ryannewcomer the "new window from prefix+c cannot accept typed input but keybindings still work" you described after building from source is a different bug; could you file a fresh issue with the exact steps + output of psmux -V and the conhost.exe version (Get-Item C:\Windows\System32\conhost.exe).VersionInfo.FileVersion)? That'll let me reproduce it cleanly.

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.

4 participants