Skip to content

Commit 500695d

Browse files
committed
fix: make terminal raw in ssh command on windows
We were making it raw, but not raw enough. It needs a few extra modes enabled to function with arrows etc.
1 parent b85d5d8 commit 500695d

File tree

3 files changed

+113
-8
lines changed

3 files changed

+113
-8
lines changed

cli/ssh.go

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

28-
"github.com/coder/retry"
29-
"github.com/coder/serpent"
30-
3128
"cdr.dev/slog"
3229
"cdr.dev/slog/sloggers/sloghuman"
33-
3430
"github.com/coder/coder/v2/cli/cliui"
3531
"github.com/coder/coder/v2/cli/cliutil"
32+
"github.com/coder/coder/v2/cli/xterminal"
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/codersdk/workspacesdk"
4037
"github.com/coder/coder/v2/cryptorand"
38+
"github.com/coder/retry"
39+
"github.com/coder/serpent"
4140
)
4241

4342
var (
@@ -341,15 +340,22 @@ func (r *RootCmd) ssh() *serpent.Command {
341340
}
342341
}
343342

344-
stdoutFile, validOut := inv.Stdout.(*os.File)
345343
stdinFile, validIn := inv.Stdin.(*os.File)
346-
if validOut && validIn && isatty.IsTerminal(stdoutFile.Fd()) {
347-
state, err := term.MakeRaw(int(stdinFile.Fd()))
344+
stdoutFile, validOut := inv.Stdout.(*os.File)
345+
if validIn && validOut && isatty.IsTerminal(stdinFile.Fd()) && isatty.IsTerminal(stdoutFile.Fd()) {
346+
inState, err := xterminal.MakeInputRaw(stdinFile.Fd())
347+
if err != nil {
348+
return err
349+
}
350+
defer func() {
351+
_ = xterminal.Restore(stdinFile.Fd(), inState)
352+
}()
353+
outState, err := xterminal.MakeOutputRaw(stdoutFile.Fd())
348354
if err != nil {
349355
return err
350356
}
351357
defer func() {
352-
_ = term.Restore(int(stdinFile.Fd()), state)
358+
_ = xterminal.Restore(stdoutFile.Fd(), outState)
353359
}()
354360

355361
windowChange := listenWindowSize(ctx)

cli/xterminal/terminal.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package xterminal
5+
6+
import (
7+
"golang.org/x/term"
8+
)
9+
10+
// State differs per-platform.
11+
type State struct {
12+
s *term.State
13+
}
14+
15+
// MakeInputRaw calls term.MakeRaw on non-Windows platforms.
16+
func MakeInputRaw(fd uintptr) (*State, error) {
17+
s, err := term.MakeRaw(int(fd))
18+
if err != nil {
19+
return nil, err
20+
}
21+
return &State{
22+
s: s,
23+
}, nil
24+
}
25+
26+
// MakeOutputRaw does nothing on non-Windows platforms.
27+
func MakeOutputRaw(_ uintptr) (*State, error) {
28+
return &State{
29+
s: nil,
30+
}, nil
31+
}
32+
33+
// Restore terminal back to original state.
34+
func Restore(fd uintptr, state *State) error {
35+
if state == nil || state.s == nil {
36+
return nil
37+
}
38+
39+
return term.Restore(int(fd), state.s)
40+
}

cli/xterminal/terminal_windows.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package xterminal
5+
6+
import (
7+
"golang.org/x/sys/windows"
8+
)
9+
10+
// State differs per-platform.
11+
type State struct {
12+
mode uint32
13+
}
14+
15+
// makeRaw sets the terminal in raw mode and returns the previous state so it can be restored.
16+
func makeRaw(handle windows.Handle, input bool) (uint32, error) {
17+
var prevState uint32
18+
if err := windows.GetConsoleMode(handle, &prevState); err != nil {
19+
return 0, err
20+
}
21+
22+
var raw uint32
23+
if input {
24+
raw = prevState &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
25+
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
26+
} else {
27+
raw = prevState | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
28+
}
29+
30+
if err := windows.SetConsoleMode(handle, raw); err != nil {
31+
return 0, err
32+
}
33+
return prevState, nil
34+
}
35+
36+
// MakeInputRaw sets an input terminal to raw and enables VT100 processing.
37+
func MakeInputRaw(handle uintptr) (*State, error) {
38+
prevState, err := makeRaw(windows.Handle(handle), true)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return &State{mode: prevState}, nil
44+
}
45+
46+
// MakeOutputRaw sets an output terminal to raw and enables VT100 processing.
47+
func MakeOutputRaw(handle uintptr) (*State, error) {
48+
prevState, err := makeRaw(windows.Handle(handle), false)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return &State{mode: prevState}, nil
54+
}
55+
56+
// Restore terminal back to original state.
57+
func Restore(handle uintptr, state *State) error {
58+
return windows.SetConsoleMode(windows.Handle(handle), state.mode)
59+
}

0 commit comments

Comments
 (0)