fix: make terminal raw in ssh command on windows (#12990)

This commit is contained in:
Dean Sheather 2024-04-17 11:01:20 -07:00 committed by GitHub
parent 92190443ff
commit d426569d4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 146 additions and 8 deletions

View File

@ -25,12 +25,8 @@ import (
"golang.org/x/xerrors"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"github.com/coder/retry"
"github.com/coder/serpent"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/coderd/autobuild/notify"
@ -38,6 +34,9 @@ import (
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/cryptorand"
"github.com/coder/coder/v2/pty"
"github.com/coder/retry"
"github.com/coder/serpent"
)
var (
@ -341,15 +340,22 @@ func (r *RootCmd) ssh() *serpent.Command {
}
}
stdoutFile, validOut := inv.Stdout.(*os.File)
stdinFile, validIn := inv.Stdin.(*os.File)
if validOut && validIn && isatty.IsTerminal(stdoutFile.Fd()) {
state, err := term.MakeRaw(int(stdinFile.Fd()))
stdoutFile, validOut := inv.Stdout.(*os.File)
if validIn && validOut && isatty.IsTerminal(stdinFile.Fd()) && isatty.IsTerminal(stdoutFile.Fd()) {
inState, err := pty.MakeInputRaw(stdinFile.Fd())
if err != nil {
return err
}
defer func() {
_ = term.Restore(int(stdinFile.Fd()), state)
_ = pty.RestoreTerminal(stdinFile.Fd(), inState)
}()
outState, err := pty.MakeOutputRaw(stdoutFile.Fd())
if err != nil {
return err
}
defer func() {
_ = pty.RestoreTerminal(stdoutFile.Fd(), outState)
}()
windowChange := listenWindowSize(ctx)

31
pty/terminal.go Normal file
View File

@ -0,0 +1,31 @@
package pty
// TerminalState differs per-platform.
type TerminalState struct {
state terminalState
}
// MakeInputRaw calls term.MakeRaw on non-Windows platforms. On Windows it sets
// special terminal modes that enable VT100 emulation as well as setting the
// same modes that term.MakeRaw sets.
//
//nolint:revive
func MakeInputRaw(fd uintptr) (*TerminalState, error) {
return makeInputRaw(fd)
}
// MakeOutputRaw does nothing on non-Windows platforms. On Windows it sets
// special terminal modes that enable VT100 emulation as well as setting the
// same modes that term.MakeRaw sets.
//
//nolint:revive
func MakeOutputRaw(fd uintptr) (*TerminalState, error) {
return makeOutputRaw(fd)
}
// RestoreTerminal restores the terminal back to its original state.
//
//nolint:revive
func RestoreTerminal(fd uintptr, state *TerminalState) error {
return restoreTerminal(fd, state)
}

36
pty/terminal_other.go Normal file
View File

@ -0,0 +1,36 @@
//go:build !windows
// +build !windows
package pty
import "golang.org/x/term"
type terminalState *term.State
//nolint:revive
func makeInputRaw(fd uintptr) (*TerminalState, error) {
s, err := term.MakeRaw(int(fd))
if err != nil {
return nil, err
}
return &TerminalState{
state: s,
}, nil
}
//nolint:revive
func makeOutputRaw(_ uintptr) (*TerminalState, error) {
// Does nothing. makeInputRaw does enough for both input and output.
return &TerminalState{
state: nil,
}, nil
}
//nolint:revive
func restoreTerminal(fd uintptr, state *TerminalState) error {
if state == nil || state.state == nil {
return nil
}
return term.Restore(int(fd), state.state)
}

65
pty/terminal_windows.go Normal file
View File

@ -0,0 +1,65 @@
//go:build windows
// +build windows
package pty
import "golang.org/x/sys/windows"
type terminalState uint32
// This is adapted from term.MakeRaw, but adds
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to the output mode and
// ENABLE_VIRTUAL_TERMINAL_INPUT to the input mode.
//
// See: https://github.com/golang/term/blob/5b15d269ba1f54e8da86c8aa5574253aea0c2198/term_windows.go#L23
//
// Copyright 2019 The Go Authors. BSD-3-Clause license. See:
// https://github.com/golang/term/blob/master/LICENSE
func makeRaw(handle windows.Handle, input bool) (uint32, error) {
var prevState uint32
if err := windows.GetConsoleMode(handle, &prevState); err != nil {
return 0, err
}
var raw uint32
if input {
raw = prevState &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
} else {
raw = prevState | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
}
if err := windows.SetConsoleMode(handle, raw); err != nil {
return 0, err
}
return prevState, nil
}
//nolint:revive
func makeInputRaw(handle uintptr) (*TerminalState, error) {
prevState, err := makeRaw(windows.Handle(handle), true)
if err != nil {
return nil, err
}
return &TerminalState{
state: terminalState(prevState),
}, nil
}
//nolint:revive
func makeOutputRaw(handle uintptr) (*TerminalState, error) {
prevState, err := makeRaw(windows.Handle(handle), false)
if err != nil {
return nil, err
}
return &TerminalState{
state: terminalState(prevState),
}, nil
}
//nolint:revive
func restoreTerminal(handle uintptr, state *TerminalState) error {
return windows.SetConsoleMode(windows.Handle(handle), uint32(state.state))
}