mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
83c186fdd4
SetConsoleMode() on input handles appears to remember invalid bits that were set, causing problems for other programs (such as xcopy.exe) trying to set the console mode after docker.exe has exited. Always restore the input console mode on set failure. Signed-off-by: John Starks <jostarks@microsoft.com>
232 lines
6.5 KiB
Go
232 lines
6.5 KiB
Go
// +build windows
|
|
|
|
package term
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/Azure/go-ansiterm/winterm"
|
|
"github.com/docker/docker/pkg/term/windows"
|
|
)
|
|
|
|
// State holds the console mode for the terminal.
|
|
type State struct {
|
|
mode uint32
|
|
}
|
|
|
|
// Winsize is used for window size.
|
|
type Winsize struct {
|
|
Height uint16
|
|
Width uint16
|
|
}
|
|
|
|
const (
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
|
enableVirtualTerminalInput = 0x0200
|
|
enableVirtualTerminalProcessing = 0x0004
|
|
disableNewlineAutoReturn = 0x0008
|
|
)
|
|
|
|
// vtInputSupported is true if enableVirtualTerminalInput is supported by the console
|
|
var vtInputSupported bool
|
|
|
|
// StdStreams returns the standard streams (stdin, stdout, stedrr).
|
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
// Turn on VT handling on all std handles, if possible. This might
|
|
// fail, in which case we will fall back to terminal emulation.
|
|
var emulateStdin, emulateStdout, emulateStderr bool
|
|
fd := os.Stdin.Fd()
|
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
// Validate that enableVirtualTerminalInput is supported, but do not set it.
|
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil {
|
|
emulateStdin = true
|
|
} else {
|
|
vtInputSupported = true
|
|
}
|
|
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
|
// remembers invalid bits on input handles.
|
|
winterm.SetConsoleMode(fd, mode)
|
|
}
|
|
|
|
fd = os.Stdout.Fd()
|
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
// Validate disableNewlineAutoReturn is supported, but do not set it.
|
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
|
|
emulateStdout = true
|
|
} else {
|
|
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
|
|
}
|
|
}
|
|
|
|
fd = os.Stderr.Fd()
|
|
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
|
// Validate disableNewlineAutoReturn is supported, but do not set it.
|
|
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
|
|
emulateStderr = true
|
|
} else {
|
|
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
|
|
}
|
|
}
|
|
|
|
if os.Getenv("ConEmuANSI") == "ON" {
|
|
// The ConEmu terminal emulates ANSI on output streams well.
|
|
emulateStdout = false
|
|
emulateStderr = false
|
|
}
|
|
|
|
if emulateStdin {
|
|
stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
|
|
} else {
|
|
stdIn = os.Stdin
|
|
}
|
|
|
|
if emulateStdout {
|
|
stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
|
|
} else {
|
|
stdOut = os.Stdout
|
|
}
|
|
|
|
if emulateStderr {
|
|
stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
|
|
} else {
|
|
stdErr = os.Stderr
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
|
func GetFdInfo(in interface{}) (uintptr, bool) {
|
|
return windows.GetHandleInfo(in)
|
|
}
|
|
|
|
// GetWinsize returns the window size based on the specified file descriptor.
|
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
winsize := &Winsize{
|
|
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
|
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
|
}
|
|
|
|
return winsize, nil
|
|
}
|
|
|
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
func IsTerminal(fd uintptr) bool {
|
|
return windows.IsConsole(fd)
|
|
}
|
|
|
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
|
// to a previous state.
|
|
func RestoreTerminal(fd uintptr, state *State) error {
|
|
return winterm.SetConsoleMode(fd, state.mode)
|
|
}
|
|
|
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
|
func SaveState(fd uintptr) (*State, error) {
|
|
mode, e := winterm.GetConsoleMode(fd)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
return &State{mode: mode}, nil
|
|
}
|
|
|
|
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
func DisableEcho(fd uintptr, state *State) error {
|
|
mode := state.mode
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
|
err := winterm.SetConsoleMode(fd, mode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Register an interrupt handler to catch and restore prior state
|
|
restoreAtInterrupt(fd, state)
|
|
return nil
|
|
}
|
|
|
|
// SetRawTerminal puts the terminal connected to the given file descriptor into
|
|
// raw mode and returns the previous state. On UNIX, this puts both the input
|
|
// and output into raw mode. On Windows, it only puts the input into raw mode.
|
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
|
state, err := MakeRaw(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Register an interrupt handler to catch and restore prior state
|
|
restoreAtInterrupt(fd, state)
|
|
return state, err
|
|
}
|
|
|
|
// SetRawTerminalOutput puts the output of terminal connected to the given file
|
|
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
|
|
// state. On Windows, it disables LF -> CRLF translation.
|
|
func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
|
state, err := SaveState(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ignore failures, since disableNewlineAutoReturn might not be supported on this
|
|
// version of Windows.
|
|
winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
|
|
return state, err
|
|
}
|
|
|
|
// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
|
|
// mode and returns the previous state of the terminal so that it can be restored.
|
|
func MakeRaw(fd uintptr) (*State, error) {
|
|
state, err := SaveState(fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mode := state.mode
|
|
|
|
// See
|
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
|
|
// Disable these modes
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
mode &^= winterm.ENABLE_LINE_INPUT
|
|
mode &^= winterm.ENABLE_MOUSE_INPUT
|
|
mode &^= winterm.ENABLE_WINDOW_INPUT
|
|
mode &^= winterm.ENABLE_PROCESSED_INPUT
|
|
|
|
// Enable these modes
|
|
mode |= winterm.ENABLE_EXTENDED_FLAGS
|
|
mode |= winterm.ENABLE_INSERT_MODE
|
|
mode |= winterm.ENABLE_QUICK_EDIT_MODE
|
|
if vtInputSupported {
|
|
mode |= enableVirtualTerminalInput
|
|
}
|
|
|
|
err = winterm.SetConsoleMode(fd, mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return state, nil
|
|
}
|
|
|
|
func restoreAtInterrupt(fd uintptr, state *State) {
|
|
sigchan := make(chan os.Signal, 1)
|
|
signal.Notify(sigchan, os.Interrupt)
|
|
|
|
go func() {
|
|
_ = <-sigchan
|
|
RestoreTerminal(fd, state)
|
|
os.Exit(0)
|
|
}()
|
|
}
|