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"
|
||||
"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)
|
||||
|
|
|
@ -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