Skip to content

Commit bf296b2

Browse files
deansheathercoadler
authored andcommitted
fix: make terminal raw in ssh command on windows (#12990)
(cherry picked from commit d426569)
1 parent b2e8b93 commit bf296b2

File tree

4 files changed

+145
-7
lines changed

4 files changed

+145
-7
lines changed

cli/ssh.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,17 @@ import (
2525
"golang.org/x/xerrors"
2626
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
2727

28-
"github.com/coder/retry"
29-
3028
"cdr.dev/slog"
3129
"cdr.dev/slog/sloggers/sloghuman"
32-
3330
"github.com/coder/coder/v2/cli/clibase"
3431
"github.com/coder/coder/v2/cli/cliui"
3532
"github.com/coder/coder/v2/cli/cliutil"
3633
"github.com/coder/coder/v2/coderd/autobuild/notify"
3734
"github.com/coder/coder/v2/coderd/util/ptr"
3835
"github.com/coder/coder/v2/codersdk"
3936
"github.com/coder/coder/v2/cryptorand"
37+
"github.com/coder/coder/v2/pty"
38+
"github.com/coder/retry"
4039
)
4140

4241
var (
@@ -339,15 +338,22 @@ func (r *RootCmd) ssh() *clibase.Cmd {
339338
}
340339
}
341340

342-
stdoutFile, validOut := inv.Stdout.(*os.File)
343341
stdinFile, validIn := inv.Stdin.(*os.File)
344-
if validOut && validIn && isatty.IsTerminal(stdoutFile.Fd()) {
345-
state, err := term.MakeRaw(int(stdinFile.Fd()))
342+
stdoutFile, validOut := inv.Stdout.(*os.File)
343+
if validIn && validOut && isatty.IsTerminal(stdinFile.Fd()) && isatty.IsTerminal(stdoutFile.Fd()) {
344+
inState, err := pty.MakeInputRaw(stdinFile.Fd())
345+
if err != nil {
346+
return err
347+
}
348+
defer func() {
349+
_ = pty.RestoreTerminal(stdinFile.Fd(), inState)
350+
}()
351+
outState, err := pty.MakeOutputRaw(stdoutFile.Fd())
346352
if err != nil {
347353
return err
348354
}
349355
defer func() {
350-
_ = term.Restore(int(stdinFile.Fd()), state)
356+
_ = pty.RestoreTerminal(stdoutFile.Fd(), outState)
351357
}()
352358

353359
windowChange := listenWindowSize(ctx)

pty/terminal.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pty
2+
3+
// TerminalState differs per-platform.
4+
type TerminalState struct {
5+
state terminalState
6+
}
7+
8+
// MakeInputRaw calls term.MakeRaw on non-Windows platforms. On Windows it sets
9+
// special terminal modes that enable VT100 emulation as well as setting the
10+
// same modes that term.MakeRaw sets.
11+
//
12+
//nolint:revive
13+
func MakeInputRaw(fd uintptr) (*TerminalState, error) {
14+
return makeInputRaw(fd)
15+
}
16+
17+
// MakeOutputRaw does nothing on non-Windows platforms. On Windows it sets
18+
// special terminal modes that enable VT100 emulation as well as setting the
19+
// same modes that term.MakeRaw sets.
20+
//
21+
//nolint:revive
22+
func MakeOutputRaw(fd uintptr) (*TerminalState, error) {
23+
return makeOutputRaw(fd)
24+
}
25+
26+
// RestoreTerminal restores the terminal back to its original state.
27+
//
28+
//nolint:revive
29+
func RestoreTerminal(fd uintptr, state *TerminalState) error {
30+
return restoreTerminal(fd, state)
31+
}

pty/terminal_other.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package pty
5+
6+
import "golang.org/x/term"
7+
8+
type terminalState *term.State
9+
10+
//nolint:revive
11+
func makeInputRaw(fd uintptr) (*TerminalState, error) {
12+
s, err := term.MakeRaw(int(fd))
13+
if err != nil {
14+
return nil, err
15+
}
16+
return &TerminalState{
17+
state: s,
18+
}, nil
19+
}
20+
21+
//nolint:revive
22+
func makeOutputRaw(_ uintptr) (*TerminalState, error) {
23+
// Does nothing. makeInputRaw does enough for both input and output.
24+
return &TerminalState{
25+
state: nil,
26+
}, nil
27+
}
28+
29+
//nolint:revive
30+
func restoreTerminal(fd uintptr, state *TerminalState) error {
31+
if state == nil || state.state == nil {
32+
return nil
33+
}
34+
35+
return term.Restore(int(fd), state.state)
36+
}

pty/terminal_windows.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package pty
5+
6+
import "golang.org/x/sys/windows"
7+
8+
type terminalState uint32
9+
10+
// This is adapted from term.MakeRaw, but adds
11+
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to the output mode and
12+
// ENABLE_VIRTUAL_TERMINAL_INPUT to the input mode.
13+
//
14+
// See: https://github.com/golang/term/blob/5b15d269ba1f54e8da86c8aa5574253aea0c2198/term_windows.go#L23
15+
//
16+
// Copyright 2019 The Go Authors. BSD-3-Clause license. See:
17+
// https://github.com/golang/term/blob/master/LICENSE
18+
func makeRaw(handle windows.Handle, input bool) (uint32, error) {
19+
var prevState uint32
20+
if err := windows.GetConsoleMode(handle, &prevState); err != nil {
21+
return 0, err
22+
}
23+
24+
var raw uint32
25+
if input {
26+
raw = prevState &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
27+
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
28+
} else {
29+
raw = prevState | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
30+
}
31+
32+
if err := windows.SetConsoleMode(handle, raw); err != nil {
33+
return 0, err
34+
}
35+
return prevState, nil
36+
}
37+
38+
//nolint:revive
39+
func makeInputRaw(handle uintptr) (*TerminalState, error) {
40+
prevState, err := makeRaw(windows.Handle(handle), true)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
return &TerminalState{
46+
state: terminalState(prevState),
47+
}, nil
48+
}
49+
50+
//nolint:revive
51+
func makeOutputRaw(handle uintptr) (*TerminalState, error) {
52+
prevState, err := makeRaw(windows.Handle(handle), false)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return &TerminalState{
58+
state: terminalState(prevState),
59+
}, nil
60+
}
61+
62+
//nolint:revive
63+
func restoreTerminal(handle uintptr, state *TerminalState) error {
64+
return windows.SetConsoleMode(windows.Handle(handle), uint32(state.state))
65+
}

0 commit comments

Comments
 (0)