|
@@ -9,14 +9,12 @@ import (
|
|
|
"syscall"
|
|
|
|
|
|
"github.com/Azure/go-ansiterm/winterm"
|
|
|
- "github.com/docker/docker/pkg/system"
|
|
|
"github.com/docker/docker/pkg/term/windows"
|
|
|
)
|
|
|
|
|
|
// State holds the console mode for the terminal.
|
|
|
type State struct {
|
|
|
- inMode, outMode uint32
|
|
|
- inHandle, outHandle syscall.Handle
|
|
|
+ mode uint32
|
|
|
}
|
|
|
|
|
|
// Winsize is used for window size.
|
|
@@ -32,143 +30,72 @@ const (
|
|
|
disableNewlineAutoReturn = 0x0008
|
|
|
)
|
|
|
|
|
|
-// usingNativeConsole is true if we are using the Windows native console
|
|
|
-var usingNativeConsole bool
|
|
|
+// 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) {
|
|
|
- switch {
|
|
|
- case os.Getenv("ConEmuANSI") == "ON":
|
|
|
- // The ConEmu terminal emulates ANSI on output streams well.
|
|
|
- return windows.ConEmuStreams()
|
|
|
- case os.Getenv("MSYSTEM") != "":
|
|
|
- // MSYS (mingw) does not emulate ANSI well.
|
|
|
- return windows.ConsoleStreams()
|
|
|
- default:
|
|
|
- if useNativeConsole() {
|
|
|
- usingNativeConsole = true
|
|
|
- return os.Stdin, os.Stdout, os.Stderr
|
|
|
+ // 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
|
|
|
}
|
|
|
- return windows.ConsoleStreams()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// useNativeConsole determines if the docker client should use the built-in
|
|
|
-// console which supports ANSI emulation, or fall-back to the golang emulator
|
|
|
-// (github.com/azure/go-ansiterm).
|
|
|
-func useNativeConsole() bool {
|
|
|
- osv := system.GetOSVersion()
|
|
|
-
|
|
|
- // Native console is not available before major version 10
|
|
|
- if osv.MajorVersion < 10 {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // Get the console modes. If this fails, we can't use the native console
|
|
|
- state, err := getNativeConsole()
|
|
|
- if err != nil {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // Probe the console to see if it can be enabled.
|
|
|
- if nil != probeNativeConsole(state) {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // Environment variable override
|
|
|
- if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
|
|
|
- if e == "1" {
|
|
|
- return 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)
|
|
|
}
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- // Must have a post-TP5 RS1 build of Windows Server 2016/Windows 10 for
|
|
|
- // the native console to be usable.
|
|
|
- if osv.Build < 14350 {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- return true
|
|
|
-}
|
|
|
-
|
|
|
-// getNativeConsole returns the console modes ('state') for the native Windows console
|
|
|
-func getNativeConsole() (State, error) {
|
|
|
- var (
|
|
|
- err error
|
|
|
- state State
|
|
|
- )
|
|
|
-
|
|
|
- // Get the handle to stdout
|
|
|
- if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
|
|
|
- return state, err
|
|
|
}
|
|
|
|
|
|
- // Get the console mode from the consoles stdout handle
|
|
|
- if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
|
|
|
- return state, err
|
|
|
+ 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)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // Get the handle to stdin
|
|
|
- if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
|
|
|
- return state, err
|
|
|
+ if os.Getenv("ConEmuANSI") == "ON" {
|
|
|
+ // The ConEmu terminal emulates ANSI on output streams well.
|
|
|
+ emulateStdout = false
|
|
|
+ emulateStderr = false
|
|
|
}
|
|
|
|
|
|
- // Get the console mode from the consoles stdin handle
|
|
|
- if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
|
|
|
- return state, err
|
|
|
+ if emulateStdin {
|
|
|
+ stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
|
|
|
+ } else {
|
|
|
+ stdIn = os.Stdin
|
|
|
}
|
|
|
|
|
|
- return state, nil
|
|
|
-}
|
|
|
-
|
|
|
-// probeNativeConsole probes the console to determine if native can be supported,
|
|
|
-func probeNativeConsole(state State) error {
|
|
|
- if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
|
|
|
- return err
|
|
|
+ if emulateStdout {
|
|
|
+ stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
|
|
|
+ } else {
|
|
|
+ stdOut = os.Stdout
|
|
|
}
|
|
|
- defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
|
|
|
|
|
|
- if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
|
|
|
- return err
|
|
|
+ if emulateStderr {
|
|
|
+ stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
|
|
|
+ } else {
|
|
|
+ stdErr = os.Stderr
|
|
|
}
|
|
|
- defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
|
|
|
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// enableNativeConsole turns on native console mode
|
|
|
-func enableNativeConsole(state State) error {
|
|
|
- // First attempt both enableVirtualTerminalProcessing and disableNewlineAutoReturn
|
|
|
- if err := winterm.SetConsoleMode(uintptr(state.outHandle),
|
|
|
- state.outMode|(enableVirtualTerminalProcessing|disableNewlineAutoReturn)); err != nil {
|
|
|
-
|
|
|
- // That may fail, so fallback to trying just enableVirtualTerminalProcessing
|
|
|
- if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
|
|
|
- winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can
|
|
|
- return err
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// disableNativeConsole turns off native console mode
|
|
|
-func disableNativeConsole(state *State) error {
|
|
|
- // Try and restore both in an out before error checking.
|
|
|
- errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
|
|
|
- errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
|
|
|
- if errout != nil {
|
|
|
- return errout
|
|
|
- }
|
|
|
- if errin != nil {
|
|
|
- return errin
|
|
|
- }
|
|
|
- return nil
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
|
@@ -199,34 +126,23 @@ func IsTerminal(fd uintptr) bool {
|
|
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
|
|
// to a previous state.
|
|
|
func RestoreTerminal(fd uintptr, state *State) error {
|
|
|
- if usingNativeConsole {
|
|
|
- return disableNativeConsole(state)
|
|
|
- }
|
|
|
- return winterm.SetConsoleMode(fd, state.outMode)
|
|
|
+ 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) {
|
|
|
- if usingNativeConsole {
|
|
|
- state, err := getNativeConsole()
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- return &state, nil
|
|
|
- }
|
|
|
-
|
|
|
mode, e := winterm.GetConsoleMode(fd)
|
|
|
if e != nil {
|
|
|
return nil, e
|
|
|
}
|
|
|
|
|
|
- return &State{outMode: mode}, nil
|
|
|
+ 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.inMode
|
|
|
+ mode := state.mode
|
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
|
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
|
|
err := winterm.SetConsoleMode(fd, mode)
|
|
@@ -239,8 +155,9 @@ func DisableEcho(fd uintptr, state *State) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// SetRawTerminal puts the terminal connected to the given file descriptor into raw
|
|
|
-// mode and returns the previous state.
|
|
|
+// 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 {
|
|
@@ -252,6 +169,21 @@ func SetRawTerminal(fd uintptr) (*State, error) {
|
|
|
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) {
|
|
@@ -260,13 +192,7 @@ func MakeRaw(fd uintptr) (*State, error) {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
- mode := state.inMode
|
|
|
- if usingNativeConsole {
|
|
|
- if err := enableNativeConsole(*state); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- mode |= enableVirtualTerminalInput
|
|
|
- }
|
|
|
+ mode := state.mode
|
|
|
|
|
|
// See
|
|
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
|
@@ -283,6 +209,9 @@ func MakeRaw(fd uintptr) (*State, error) {
|
|
|
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 {
|