mirror of https://github.com/coder/coder.git
fix: make terminal raw in ssh command on windows (#12990)
This commit is contained in:
parent
92190443ff
commit
d426569d4a
22
cli/ssh.go
22
cli/ssh.go
|
@ -25,12 +25,8 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
|
||||||
"github.com/coder/retry"
|
|
||||||
"github.com/coder/serpent"
|
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
"github.com/coder/coder/v2/cli/cliutil"
|
"github.com/coder/coder/v2/cli/cliutil"
|
||||||
"github.com/coder/coder/v2/coderd/autobuild/notify"
|
"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"
|
||||||
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/cryptorand"
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
|
"github.com/coder/coder/v2/pty"
|
||||||
|
"github.com/coder/retry"
|
||||||
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -341,15 +340,22 @@ func (r *RootCmd) ssh() *serpent.Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdoutFile, validOut := inv.Stdout.(*os.File)
|
|
||||||
stdinFile, validIn := inv.Stdin.(*os.File)
|
stdinFile, validIn := inv.Stdin.(*os.File)
|
||||||
if validOut && validIn && isatty.IsTerminal(stdoutFile.Fd()) {
|
stdoutFile, validOut := inv.Stdout.(*os.File)
|
||||||
state, err := term.MakeRaw(int(stdinFile.Fd()))
|
if validIn && validOut && isatty.IsTerminal(stdinFile.Fd()) && isatty.IsTerminal(stdoutFile.Fd()) {
|
||||||
|
inState, err := pty.MakeInputRaw(stdinFile.Fd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
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)
|
windowChange := listenWindowSize(ctx)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
Loading…
Reference in New Issue