github.com/reds/docker@v1.11.2-rc1/pkg/term/term_windows.go (about)

     1  // +build windows
     2  
     3  package term
     4  
     5  import (
     6  	"io"
     7  	"os"
     8  	"os/signal"
     9  	"syscall"
    10  
    11  	"github.com/Azure/go-ansiterm/winterm"
    12  	"github.com/docker/docker/pkg/system"
    13  	"github.com/docker/docker/pkg/term/windows"
    14  )
    15  
    16  // State holds the console mode for the terminal.
    17  type State struct {
    18  	inMode, outMode     uint32
    19  	inHandle, outHandle syscall.Handle
    20  }
    21  
    22  // Winsize is used for window size.
    23  type Winsize struct {
    24  	Height uint16
    25  	Width  uint16
    26  	x      uint16
    27  	y      uint16
    28  }
    29  
    30  const (
    31  	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
    32  	enableVirtualTerminalInput      = 0x0200
    33  	enableVirtualTerminalProcessing = 0x0004
    34  )
    35  
    36  // usingNativeConsole is true if we are using the Windows native console
    37  var usingNativeConsole bool
    38  
    39  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    40  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    41  	switch {
    42  	case os.Getenv("ConEmuANSI") == "ON":
    43  		// The ConEmu terminal emulates ANSI on output streams well.
    44  		return windows.ConEmuStreams()
    45  	case os.Getenv("MSYSTEM") != "":
    46  		// MSYS (mingw) does not emulate ANSI well.
    47  		return windows.ConsoleStreams()
    48  	default:
    49  		if useNativeConsole() {
    50  			usingNativeConsole = true
    51  			return os.Stdin, os.Stdout, os.Stderr
    52  		}
    53  		return windows.ConsoleStreams()
    54  	}
    55  }
    56  
    57  // useNativeConsole determines if the docker client should use the built-in
    58  // console which supports ANSI emulation, or fall-back to the golang emulator
    59  // (github.com/azure/go-ansiterm).
    60  func useNativeConsole() bool {
    61  	osv, err := system.GetOSVersion()
    62  	if err != nil {
    63  		return false
    64  	}
    65  
    66  	// Native console is not available before major version 10
    67  	if osv.MajorVersion < 10 {
    68  		return false
    69  	}
    70  
    71  	// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
    72  	if osv.Build < 10578 {
    73  		return false
    74  	}
    75  
    76  	// Get the console modes. If this fails, we can't use the native console
    77  	state, err := getNativeConsole()
    78  	if err != nil {
    79  		return false
    80  	}
    81  
    82  	// Probe the console to see if it can be enabled.
    83  	if nil != probeNativeConsole(state) {
    84  		return false
    85  	}
    86  
    87  	// Environment variable override
    88  	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
    89  		if e == "1" {
    90  			return true
    91  		}
    92  		return false
    93  	}
    94  
    95  	// TODO Windows. The native emulator still has issues which
    96  	// mean it shouldn't be enabled for everyone. Change this next line to true
    97  	// to change the default to "enable if available". In the meantime, users
    98  	// can still try it out by using USE_NATIVE_CONSOLE env variable.
    99  	return false
   100  }
   101  
   102  // getNativeConsole returns the console modes ('state') for the native Windows console
   103  func getNativeConsole() (State, error) {
   104  	var (
   105  		err   error
   106  		state State
   107  	)
   108  
   109  	// Get the handle to stdout
   110  	if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
   111  		return state, err
   112  	}
   113  
   114  	// Get the console mode from the consoles stdout handle
   115  	if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
   116  		return state, err
   117  	}
   118  
   119  	// Get the handle to stdin
   120  	if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
   121  		return state, err
   122  	}
   123  
   124  	// Get the console mode from the consoles stdin handle
   125  	if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
   126  		return state, err
   127  	}
   128  
   129  	return state, nil
   130  }
   131  
   132  // probeNativeConsole probes the console to determine if native can be supported,
   133  func probeNativeConsole(state State) error {
   134  	if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
   135  		return err
   136  	}
   137  	defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
   138  
   139  	if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
   140  		return err
   141  	}
   142  	defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
   143  
   144  	return nil
   145  }
   146  
   147  // enableNativeConsole turns on native console mode
   148  func enableNativeConsole(state State) error {
   149  	if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
   150  		return err
   151  	}
   152  
   153  	if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
   154  		winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can
   155  		return err
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // disableNativeConsole turns off native console mode
   162  func disableNativeConsole(state *State) error {
   163  	// Try and restore both in an out before error checking.
   164  	errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
   165  	errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
   166  	if errout != nil {
   167  		return errout
   168  	}
   169  	if errin != nil {
   170  		return errin
   171  	}
   172  	return nil
   173  }
   174  
   175  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
   176  func GetFdInfo(in interface{}) (uintptr, bool) {
   177  	return windows.GetHandleInfo(in)
   178  }
   179  
   180  // GetWinsize returns the window size based on the specified file descriptor.
   181  func GetWinsize(fd uintptr) (*Winsize, error) {
   182  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	winsize := &Winsize{
   188  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
   189  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
   190  		x:      0,
   191  		y:      0}
   192  
   193  	return winsize, nil
   194  }
   195  
   196  // IsTerminal returns true if the given file descriptor is a terminal.
   197  func IsTerminal(fd uintptr) bool {
   198  	return windows.IsConsole(fd)
   199  }
   200  
   201  // RestoreTerminal restores the terminal connected to the given file descriptor
   202  // to a previous state.
   203  func RestoreTerminal(fd uintptr, state *State) error {
   204  	if usingNativeConsole {
   205  		return disableNativeConsole(state)
   206  	}
   207  	return winterm.SetConsoleMode(fd, state.outMode)
   208  }
   209  
   210  // SaveState saves the state of the terminal connected to the given file descriptor.
   211  func SaveState(fd uintptr) (*State, error) {
   212  	if usingNativeConsole {
   213  		state, err := getNativeConsole()
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		return &state, nil
   218  	}
   219  
   220  	mode, e := winterm.GetConsoleMode(fd)
   221  	if e != nil {
   222  		return nil, e
   223  	}
   224  
   225  	return &State{outMode: mode}, nil
   226  }
   227  
   228  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   229  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   230  func DisableEcho(fd uintptr, state *State) error {
   231  	mode := state.inMode
   232  	mode &^= winterm.ENABLE_ECHO_INPUT
   233  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   234  	err := winterm.SetConsoleMode(fd, mode)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	// Register an interrupt handler to catch and restore prior state
   240  	restoreAtInterrupt(fd, state)
   241  	return nil
   242  }
   243  
   244  // SetRawTerminal puts the terminal connected to the given file descriptor into raw
   245  // mode and returns the previous state.
   246  func SetRawTerminal(fd uintptr) (*State, error) {
   247  	state, err := MakeRaw(fd)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	// Register an interrupt handler to catch and restore prior state
   253  	restoreAtInterrupt(fd, state)
   254  	return state, err
   255  }
   256  
   257  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   258  // mode and returns the previous state of the terminal so that it can be restored.
   259  func MakeRaw(fd uintptr) (*State, error) {
   260  	state, err := SaveState(fd)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	mode := state.inMode
   266  	if usingNativeConsole {
   267  		if err := enableNativeConsole(*state); err != nil {
   268  			return nil, err
   269  		}
   270  		mode |= enableVirtualTerminalInput
   271  	}
   272  
   273  	// See
   274  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   275  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   276  
   277  	// Disable these modes
   278  	mode &^= winterm.ENABLE_ECHO_INPUT
   279  	mode &^= winterm.ENABLE_LINE_INPUT
   280  	mode &^= winterm.ENABLE_MOUSE_INPUT
   281  	mode &^= winterm.ENABLE_WINDOW_INPUT
   282  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   283  
   284  	// Enable these modes
   285  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   286  	mode |= winterm.ENABLE_INSERT_MODE
   287  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   288  
   289  	err = winterm.SetConsoleMode(fd, mode)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	return state, nil
   294  }
   295  
   296  func restoreAtInterrupt(fd uintptr, state *State) {
   297  	sigchan := make(chan os.Signal, 1)
   298  	signal.Notify(sigchan, os.Interrupt)
   299  
   300  	go func() {
   301  		_ = <-sigchan
   302  		RestoreTerminal(fd, state)
   303  		os.Exit(0)
   304  	}()
   305  }