Skip to content

Fix native-Windows input parity and terminal restore - #56

Merged
l1a merged 7 commits into
mainfrom
fix/windows-input-and-terminal-restore
Jul 21, 2026
Merged

Fix native-Windows input parity and terminal restore#56
l1a merged 7 commits into
mainfrom
fix/windows-input-and-terminal-restore

Conversation

@l1a

@l1a l1a commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the native-Windows client's remaining feature-parity gaps with SSH for basic interactive use. Four independent fixes, all in src/bin/etr.rs (client only — no server/protocol changes; existing etrs is fully compatible):

  1. Special characters no longer "eaten" (zellij no longer needs ^G). The stdin reader used std::io::stdin().read(), which on Windows goes through Rust std's ReadConsoleW shim and drops bytes that aren't clean UTF-8. It now reads the console input handle directly with ReadFile (with ENABLE_VIRTUAL_TERMINAL_INPUT), delivering every keystroke — control chars, arrows, Unicode — intact. Input codepage is set to UTF-8 so typed multi-byte input reaches the remote correctly.
  2. First line now echoes as typed (Windows client: first line of input not echoed until Enter #54). The reader issued its first ReadFile before raw mode was enabled (raw mode is only turned on after the QUIC connect), so a cooked/line-mode read held the first line until Enter. The Windows reader now waits until raw + VT-input mode is active before its first read.
  3. Local terminal restored on exit. A remote full-screen app (zellij/vim/less) leaves the local terminal in alternate-screen/mouse-reporting/bracketed-paste/hidden-cursor modes; on a hard drop or ~. those weren't undone (mouse wheel spewed escapes, cursor hidden). The client now emits VT resets on exit — a cursor-safe set on every exit and a screen-restoring set only on unclean exits.
  4. Local shell's Enter works again after etr exits. crossterm's disable_raw_mode doesn't clear the ENABLE_VIRTUAL_TERMINAL_INPUT flag enable_vt_console sets, so the console was left in VT-input mode and the local shell echoed characters but wouldn't accept Enter. The client now captures the console's exact original modes/codepage once and restores them verbatim on exit.

All fixes are #[cfg(windows)]-scoped or no-ops on Unix.

Test plan

  • just pr gate passes: fmt, clippy -D warnings, 112 tests, man pages build.
  • Live-verified Windows → WSL Fedora 44 etrs with synthesized real console key events (WriteConsoleInputW):
    • Isolated harness: a, Ctrl+G0x07, Up-arrow→ESC [ A, rapid burst, é→UTF-8 all delivered intact via the raw+VT ReadFile path.
    • Full session: injected keystrokes reached the remote per-keystroke (remote zsh-syntax-highlighting recoloured char-by-char); first line echoed before Enter (A/B pre-Enter snapshot); on exit the client emitted the cursor-safe reset.
    • Console mode restored byte-identical after exit (0x01f70x01f7), VT-input flag cleared.
  • User confirmed on their own Windows→remote setup: special chars work without ^G, first-line echo fixed, terminal usable after shutdown.

Notes

  • NOTES.md and the wiki Troubleshooting page updated.
  • Known follow-ups recorded in NOTES (not addressed here): clean shell exit can race the server Disconnect and make etr reconnect instead of quit (server-side); just recipes need cygpath on native Windows shells; etr host 'cmd' </dev/null truncates on stdin-EOF.

l1a added 7 commits July 21, 2026 13:43
Two independent native-Windows parity fixes in the etr client.

1. Special characters no longer "eaten" (issue #54): the stdin reader used
   std::io::stdin().read(), which on Windows goes through Rust std's
   ReadConsoleW shim (UTF-16->UTF-8 + line cooking). Even in raw mode it
   batches input and drops non-UTF-8 bytes, which made special characters
   vanish (zellij keybindings needing ^g) and caused the first-line-not-
   echoed bug. It now reads the console input handle directly with ReadFile
   (read_stdin); with ENABLE_VIRTUAL_TERMINAL_INPUT on this returns the same
   unbatched, per-keystroke VT byte stream a Unix terminal emits.
   enable_vt_console also sets the console input codepage to UTF-8 (65001),
   saved/restored on exit, so typed multi-byte input reaches the remote as
   UTF-8. Adds windows-sys feature Win32_Storage_FileSystem for ReadFile.

2. Local terminal restored on exit: a remote full-screen app leaves the
   local terminal in alternate-screen/mouse/paste/hidden-cursor modes that
   disable_raw_mode does not undo, so after a hard drop or ~. the mouse wheel
   spewed escapes and the terminal was unusable. restore_terminal() now emits
   VT resets on every final-exit path: a cursor-safe part (TERM_RESET_MODES)
   on every exit and a screen-restoring part (TERM_RESET_SCREEN, which homes
   the cursor) only on unclean exits. Avoids a full RIS so scrollback is kept.

Version 0.6.4 -> 0.6.5. Test count 110 -> 112 (reset-sequence regressions).

Assisted-By: Claude Opus 4.8
Verified end-to-end against a real Unix etrs (WSL Fedora 44): remote prompt
renders, PTY command round-trips, and the client emits the cursor-safe
terminal-restore sequence on clean exit (fix #2 confirmed in the live byte
stream). The console input-VT path (fix #1) needs interactive keystrokes and
is flagged for manual confirmation. Also notes an adjacent pre-existing gap:
redirected stdin ends a remote-command session on EOF before output arrives.

Assisted-By: Claude Opus 4.8
Drove the live etr client with real console key events (WriteConsoleInputW)
against a WSL Fedora 44 etrs. Confirms fix #1 end-to-end: Ctrl+G->0x07, arrow
keys, rapid bursts and Unicode all survive the raw+VT-input ReadFile path
un-eaten, and injected keystrokes reach the remote per-keystroke (remote
zsh-syntax-highlighting recolours char-by-char). Confirms fix #2: cursor-safe
terminal-restore sequence emitted on clean exit. Updates NOTES accordingly.

Assisted-By: Claude Opus 4.8
Promote the redirected-stdin truncation note from the v0.6.5 verification
footnote into the Known gaps / next steps list so it is discoverable as
tracked open work, with a sketch of the ssh-parity fix (half-close stdin,
keep draining PTY output).

Assisted-By: Claude Opus 4.8
`just install` (and other bash-shebang recipes) fail from PowerShell/nushell
with "could not find cygpath": just tries to translate the shebang interpreter
path via cygpath, absent without Git Bash on PATH. Recorded in Known gaps with
the cargo-install workaround and a sketch of a cross-shell fix.

Assisted-By: Claude Opus 4.8
The single stdin reader thread is spawned before the QUIC connect, but raw +
VT-input mode is only enabled after connect. On Windows a ReadFile issued while
the console is still in cooked/line mode stays line-buffered for that whole
read, so the first line was held client-side until Enter ("no echo until first
Enter"). The v0.6.5 ReadFile change did not fix this — it is a timing problem,
not a read-mechanism one.

Gate the Windows reader on a one-shot signal fired right after the first
enable_raw_mode + enable_vt_console, so its first read happens in raw + VT mode
and is per-keystroke. Unix is unaffected (ungated, never had the bug).

Verified with an A/B console-keystroke harness that snapshots the client's
stdout before Enter: pre-fix the typed first line is absent (line-buffered);
with the gate it appears, echoed back per-keystroke. NOTES corrected (the
earlier claim that ReadFile alone fixed #54 was wrong).

Assisted-By: Claude Opus 4.8
enable_vt_console sets ENABLE_VIRTUAL_TERMINAL_INPUT, but crossterm's
disable_raw_mode only ORs the line/echo/processed-input bits back — it never
clears the VT-input flag. So after etr exited, the console was left with
VT-input enabled and the local shell echoed typed characters but would not
accept Enter (the VT-translated Enter wasn't seen as line submission).

Capture the console's exact original input/output modes + input codepage once
(capture_console_originals, before raw mode is first enabled) and restore them
verbatim on every exit path (restore_console_state), which clears the leftover
VT-input flag. Verified with a harness: input mode restored byte-identical
(0x01f7 -> 0x01f7), VT_INPUT not left set. Pre-existing since v0.6.4; no-op on
Unix. NOTES also records a related server-side gap (clean shell `exit`
sometimes reconnects instead of quitting because the Disconnect races the
connection close).

Assisted-By: Claude Opus 4.8
@l1a
l1a merged commit 4fe7534 into main Jul 21, 2026
23 checks passed
@l1a
l1a deleted the fix/windows-input-and-terminal-restore branch July 21, 2026 22:58
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