Skip to content

feat(pty): expose terminal modes in SSHPtyConfig#164

Open
wonkyungup wants to merge 2 commits into
TerminalStudio:masterfrom
wonkyungup:master
Open

feat(pty): expose terminal modes in SSHPtyConfig#164
wonkyungup wants to merge 2 commits into
TerminalStudio:masterfrom
wonkyungup:master

Conversation

@wonkyungup

Copy link
Copy Markdown

Summary

Adds an optional modes: Map<int, int>? field to SSHPtyConfig and wires it through SSHClient.shell() and
SSHClient.execute() via the existing terminalModes parameter of sendPtyReq — which previously had no
public API to populate.

Motivation

The SSH pty-req message (RFC 4254 §8) carries an "encoded terminal modes" field — opcode/value pairs that
let the client request specific termios settings at PTY creation time. Without exposing this at the API
level, callers have no way to control PTY behavior before the first byte hits the line discipline.

The concrete use case that motivated this: a terminal client that injects shell integration (OSC 7 cwd
reporting, prompt hooks) right after connection. Doing client.shell() then writing stty -echo; <setup>; stty echo\n doesn't work — the PTY echoes input character-by-character until the newline arrives, by which
time the entire setup line has already been streamed to the user's screen. The only race-free fix is to
disable ECHO before the PTY is created, which requires sending mode 53 (ECHO) with value 0 in the
initial pty-req.

Other common uses: {51: 0, 53: 0} for raw-mode TUIs, custom VINTR/VERASE mappings, etc.

Changes

  • SSHPtyConfig: new final Map<int, int>? modes field and constructor parameter.
  • SSHPtyConfig.encodeModes(): encodes the map into the byte string format defined by RFC 4254 §8 —
    opcode(1) + value(4 BE) per entry, terminated by TTY_OP_END (0x00). Returns Uint8List(0) when modes is
    null or empty.
  • SSHClient.shell() and SSHClient.execute(): pass pty.encodeModes() as terminalModes to sendPtyReq.

Backward compatibility

Fully backward compatible. modes is optional and defaults to null; encodeModes() returns an empty byte
string in that case, which is identical to the previous behavior (sendPtyReq already defaulted
terminalModes to Uint8List(0)).

Example

final session = await client.shell(
  pty: const SSHPtyConfig(
    type: 'xterm-256color',
    width: 120,
    height: 40,
    modes: {53: 0}, // ECHO off at PTY creation
  ),
);

Spec

RFC 4254 §8 — https://datatracker.ietf.org/doc/html/rfc4254#section-8

wonkyungup and others added 2 commits June 7, 2026 14:26
Add `modes: Map<int, int>?` to SSHPtyConfig and wire it through both
`shell()` and `execute()` so callers can request a PTY with specific
termios settings (RFC 4254 §8) — e.g. `{53: 0}` to disable ECHO at
pty-req time, avoiding the race where `stty -echo` runs only after the
shell already echoed the rest of the input line.

`encodeModes()` builds the standard opcode(1) + value(4 BE) bytes
terminated by TTY_OP_END (0x00). Empty/absent modes yield an empty
byte string, preserving prior behavior.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removes the inline `<--- 추가` annotations left over from local
prototyping and adds an Unreleased CHANGELOG entry describing the
`SSHPtyConfig.modes` field introduced in the previous commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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