// +build windows package term import ( "io" "os" "os/signal" "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 } // Winsize is used for window size. type Winsize struct { Height uint16 Width uint16 x 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). 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 } 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, err := system.GetOSVersion() if err != nil { return false } // Native console is not available before major version 10 if osv.MajorVersion < 10 { return false } // Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later if osv.Build < 10578 { 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 } return false } // TODO Windows. The native emulator still has issues which // 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 // can still try it out by using USE_NATIVE_CONSOLE env variable. 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. 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), x: 0, y: 0} 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 { if usingNativeConsole { return disableNativeConsole(state) } return winterm.SetConsoleMode(fd, state.outMode) } // 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 } // 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 &^= 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. 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 } // 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.inMode if usingNativeConsole { if err := enableNativeConsole(*state); err != nil { return nil, err } mode |= enableVirtualTerminalInput } // 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 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) }() }