Skip to content

fix: keep echo on in the cooked and well-done modes - #50

Merged
passcod merged 1 commit into
watchexec:mainfrom
Marukome0743:fix-echo-in-cooked-modes
Aug 26, 2026
Merged

fix: keep echo on in the cooked and well-done modes#50
passcod merged 1 commit into
watchexec:mainfrom
Marukome0743:fix-echo-in-cooked-modes

Conversation

@Marukome0743

Copy link
Copy Markdown
Contributor

Fixes #49.

What was wrong

vt_cooked() and vt_well_done() go through write_termios, which reads the
current attributes, wipes every flag field, and re-inserts a hardcoded set. That
set never contained ECHO, so after either mode runs the terminal accepts input
without showing it. VtWellDone is what watchexec --clear=reset applies, which
is how this reaches users — the symptom is reported downstream at
mise#8269.

Neither of the two commands people reach for fully undoes it: stty sane restores
the echo flags but leaves ISTRIP set, and reset clears ISTRIP but leaves
IEXTEN off. Issue #49 has the measurements.

What this changes

  • Set the echo flags. ECHO, ECHOE, ECHOK, and ECHOCTL/ECHOKE on the
    platforms where nix exposes them, plus IEXTEN. Both modes share one
    usable_local_flags() so they cannot drift apart.
  • Stop setting ISTRIP. It clears the eighth bit of every input byte, so no
    multi-byte UTF-8 sequence can survive to be interpreted as UTF-8 — which
    contradicted the IUTF8 that vt_well_done sets beside it.
  • Carry the character size and the line speed across the reset. They describe
    the line rather than how the terminal behaves on it, and a screen clear has no
    way to know what to put back. The wipe left CSIZE at CS5 and, on Linux where
    the speed lives in the control flags, the baud rate at B0. A pty absorbs most
    of that, but on a real serial console B0 means hang up the line.

The rest of each mode is unchanged, and the doc comments are updated to match —
they enumerate the flags, so they were describing the old behaviour.

I went with keeping the authoritative from-scratch write rather than switching to
read-and-modify, since that design is deliberate and documented. If you would
rather have the other approach, I am happy to redo it.

Structure

The two mode bodies move from closures into named cooked_mode / well_done_mode
functions, and the duplicated body of write_termios moves into a
write_termios_to(fd, f) that takes the descriptor. That removes the copy-pasted
tty//dev/tty branches and lets the tests drive the real mode functions against a
descriptor of their own.

Tests

Four unit tests, each run against both modes, on a pty from nix::pty::openpty so
they never touch the test runner's own terminal:

  • echo_survives — the echo flags, IEXTEN, ICANON and ISIG are all set afterwards
  • the_eighth_bit_is_not_strippedISTRIP is not set afterwards
  • the_line_configuration_is_left_aloneCSIZE and the speed come out as they went in
  • a_terminal_already_without_echo_is_recovered — starting from a terminal with every
    local flag cleared, which is what these modes exist to rescue, still yields echo

nix::pty is behind the term feature, which is already enabled, so there is no
dependency change. The test module is cfg'd off the targets where openpty or
nix::pty do not exist (redox, fuchsia, aix), matching the crate's existing
target handling.

What I could not verify locally

I have no Rust toolchain on this machine, so I have not compiled this — I checked
every API against the nix 0.31.3 sources (tcgetattr/tcsetattr are generic over
AsFd, openpty is term-gated and absent on aix, ECHOCTL/ECHOKE are absent
on redox, libc_bitflags! derives Debug), and formatted with rustfmt against the
repo's .rustfmt.toml. CI is the real check here.

@passcod

passcod commented Aug 26, 2026

Copy link
Copy Markdown
Member

Co-Authored-By on the commit please :)

`VtCooked` and `VtWellDone` wipe every termios flag field and re-insert a
hardcoded set that never contained `ECHO`, so applying either left a terminal
that accepts input without showing it — the state people run `reset` to escape.
`VtWellDone` is what `watchexec --clear=reset` applies, which is how this
reaches users.

Set the echo flags (`ECHO`, `ECHOE`, `ECHOK`, plus `ECHOCTL`/`ECHOKE` where
they exist) along with `IEXTEN`, and stop setting `ISTRIP`: it clears the
eighth bit of every input byte, which no multi-byte UTF-8 sequence survives,
and it contradicted the `IUTF8` set beside it.

Also carry the character size and the line speed across the reset rather than
wiping them. Those describe the line itself rather than how the terminal
behaves on it, and clearing the screen has no way to know what to put back:
the wipe left `CSIZE` at `CS5` and, on Linux where the speed lives in the
control flags, the baud rate at `B0` — which on a real serial line means
"hang up".

The mode bodies move into named functions so the new pty tests exercise the
real ones rather than a copy of their contents.

Fixes #49
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Marukome0743
Marukome0743 force-pushed the fix-echo-in-cooked-modes branch from d99bbbd to 311d6d3 Compare August 26, 2026 05:46
@Marukome0743

Copy link
Copy Markdown
Contributor Author

Co-Authored-By on the commit please :)

Thanks for the quick review!

Added a Co-authored-by: Claude Opus 5 <noreply@anthropic.com> trailer and force-pushed.
The code itself is unchanged — commit message only.

@passcod
passcod enabled auto-merge (squash) August 26, 2026 07:36
@passcod
passcod merged commit 0e5d12a into watchexec:main Aug 26, 2026
6 checks passed
@Marukome0743
Marukome0743 deleted the fix-echo-in-cooked-modes branch August 26, 2026 07:52
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.

VtWellDone and VtCooked leave the terminal with ECHO off

2 participants