1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Windows: VirtualTerminalInput native console

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2015-11-11 15:04:40 -08:00
parent 402ba93f68
commit 33729d3b5a

View file

@ -15,7 +15,8 @@ import (
// State holds the console mode for the terminal. // State holds the console mode for the terminal.
type State struct { type State struct {
mode uint32 inMode, outMode uint32
inHandle, outHandle syscall.Handle
} }
// Winsize is used for window size. // Winsize is used for window size.
@ -26,6 +27,15 @@ type Winsize struct {
y uint16 y uint16
} }
const (
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
enableVirtualTerminalInput = 0x0200
enableVirtualTerminalProcessing = 0x0004
)
// usingNativeConsole is true if we are using the Windows native console
var usingNativeConsole bool
// StdStreams returns the standard streams (stdin, stdout, stedrr). // StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
switch { switch {
@ -37,6 +47,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
return windows.ConsoleStreams() return windows.ConsoleStreams()
default: default:
if useNativeConsole() { if useNativeConsole() {
usingNativeConsole = true
return os.Stdin, os.Stdout, os.Stderr return os.Stdin, os.Stdout, os.Stderr
} }
return windows.ConsoleStreams() return windows.ConsoleStreams()
@ -52,7 +63,7 @@ func useNativeConsole() bool {
return false return false
} }
// Native console is not available major version 10 // Native console is not available before major version 10
if osv.MajorVersion < 10 { if osv.MajorVersion < 10 {
return false return false
} }
@ -62,6 +73,17 @@ func useNativeConsole() bool {
return false 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 // Environment variable override
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" { if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
if e == "1" { if e == "1" {
@ -70,32 +92,86 @@ func useNativeConsole() bool {
return false return false
} }
// Get the handle to stdout // TODO Windows. The native emulator still has issues which
stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
if err != nil {
return false
}
// Get the console mode from the consoles stdout handle
var mode uint32
if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
return false
}
// Legacy mode does not have native ANSI emulation.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
const enableVirtualTerminalProcessing = 0x0004
if mode&enableVirtualTerminalProcessing == 0 {
return false
}
// TODO Windows (Post TP4). The native emulator still has issues which
// mean it shouldn't be enabled for everyone. Change this next line to true // mean it shouldn't be enabled for everyone. Change this next line to true
// to change the default to "enable if available". In the meantime, users // to change the default to "enable if available". In the meantime, users
// can still try it out by using USE_NATIVE_CONSOLE env variable. // can still try it out by using USE_NATIVE_CONSOLE env variable.
return false return false
} }
// 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
}
// Get the handle to stdin
if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
return state, err
}
// Get the console mode from the consoles stdin handle
if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
return state, err
}
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
}
defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
return err
}
defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
return nil
}
// enableNativeConsole turns on native console mode
func enableNativeConsole(state State) error {
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
}
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) { func GetFdInfo(in interface{}) (uintptr, bool) {
return windows.GetHandleInfo(in) return windows.GetHandleInfo(in)
@ -103,7 +179,6 @@ func GetFdInfo(in interface{}) (uintptr, bool) {
// GetWinsize returns the window size based on the specified file descriptor. // GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) { func GetWinsize(fd uintptr) (*Winsize, error) {
info, err := winterm.GetConsoleScreenBufferInfo(fd) info, err := winterm.GetConsoleScreenBufferInfo(fd)
if err != nil { if err != nil {
return nil, err return nil, err
@ -115,9 +190,6 @@ func GetWinsize(fd uintptr) (*Winsize, error) {
x: 0, x: 0,
y: 0} y: 0}
// Note: GetWinsize is called frequently -- uncomment only for excessive details
// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
return winsize, nil return winsize, nil
} }
@ -129,25 +201,36 @@ func IsTerminal(fd uintptr) bool {
// RestoreTerminal restores the terminal connected to the given file descriptor // RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state. // to a previous state.
func RestoreTerminal(fd uintptr, state *State) error { func RestoreTerminal(fd uintptr, state *State) error {
return winterm.SetConsoleMode(fd, state.mode) if usingNativeConsole {
return disableNativeConsole(state)
}
return winterm.SetConsoleMode(fd, state.outMode)
} }
// SaveState saves the state of the terminal connected to the given file descriptor. // SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) { 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) mode, e := winterm.GetConsoleMode(fd)
if e != nil { if e != nil {
return nil, e return nil, e
} }
return &State{mode}, nil
return &State{outMode: mode}, nil
} }
// DisableEcho disables echo for the terminal connected to the given file descriptor. // 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 // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error { func DisableEcho(fd uintptr, state *State) error {
mode := state.mode mode := state.inMode
mode &^= winterm.ENABLE_ECHO_INPUT mode &^= winterm.ENABLE_ECHO_INPUT
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
err := winterm.SetConsoleMode(fd, mode) err := winterm.SetConsoleMode(fd, mode)
if err != nil { if err != nil {
return err return err
@ -179,10 +262,17 @@ func MakeRaw(fd uintptr) (*State, error) {
return nil, err return nil, err
} }
mode := state.inMode
if usingNativeConsole {
if err := enableNativeConsole(*state); err != nil {
return nil, err
}
mode |= enableVirtualTerminalInput
}
// See // See
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx // -- 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 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
mode := state.mode
// Disable these modes // Disable these modes
mode &^= winterm.ENABLE_ECHO_INPUT mode &^= winterm.ENABLE_ECHO_INPUT