github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/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  	mode uint32
    19  }
    20  
    21  // Winsize is used for window size.
    22  type Winsize struct {
    23  	Height uint16
    24  	Width  uint16
    25  	x      uint16
    26  	y      uint16
    27  }
    28  
    29  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    30  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    31  	switch {
    32  	case os.Getenv("ConEmuANSI") == "ON":
    33  		// The ConEmu shell emulates ANSI well by default.
    34  		return os.Stdin, os.Stdout, os.Stderr
    35  	case os.Getenv("MSYSTEM") != "":
    36  		// MSYS (mingw) does not emulate ANSI well.
    37  		return windows.ConsoleStreams()
    38  	default:
    39  		if useNativeConsole() {
    40  			return os.Stdin, os.Stdout, os.Stderr
    41  		}
    42  		return windows.ConsoleStreams()
    43  	}
    44  }
    45  
    46  // useNativeConsole determines if the docker client should use the built-in
    47  // console which supports ANSI emulation, or fall-back to the golang emulator
    48  // (github.com/azure/go-ansiterm).
    49  func useNativeConsole() bool {
    50  	osv, err := system.GetOSVersion()
    51  	if err != nil {
    52  		return false
    53  	}
    54  
    55  	// Native console is not available major version 10
    56  	if osv.MajorVersion < 10 {
    57  		return false
    58  	}
    59  
    60  	// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
    61  	if osv.Build < 10578 {
    62  		return false
    63  	}
    64  
    65  	// Environment variable override
    66  	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
    67  		if e == "1" {
    68  			return true
    69  		}
    70  		return false
    71  	}
    72  
    73  	// Get the handle to stdout
    74  	stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
    75  	if err != nil {
    76  		return false
    77  	}
    78  
    79  	// Get the console mode from the consoles stdout handle
    80  	var mode uint32
    81  	if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
    82  		return false
    83  	}
    84  
    85  	// Legacy mode does not have native ANSI emulation.
    86  	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
    87  	const enableVirtualTerminalProcessing = 0x0004
    88  	if mode&enableVirtualTerminalProcessing == 0 {
    89  		return false
    90  	}
    91  
    92  	// TODO Windows (Post TP4). The native emulator still has issues which
    93  	// mean it shouldn't be enabled for everyone. Change this next line to true
    94  	// to change the default to "enable if available". In the meantime, users
    95  	// can still try it out by using USE_NATIVE_CONSOLE env variable.
    96  	return false
    97  }
    98  
    99  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
   100  func GetFdInfo(in interface{}) (uintptr, bool) {
   101  	return windows.GetHandleInfo(in)
   102  }
   103  
   104  // GetWinsize returns the window size based on the specified file descriptor.
   105  func GetWinsize(fd uintptr) (*Winsize, error) {
   106  
   107  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	winsize := &Winsize{
   113  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
   114  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
   115  		x:      0,
   116  		y:      0}
   117  
   118  	// Note: GetWinsize is called frequently -- uncomment only for excessive details
   119  	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
   120  	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
   121  	return winsize, nil
   122  }
   123  
   124  // IsTerminal returns true if the given file descriptor is a terminal.
   125  func IsTerminal(fd uintptr) bool {
   126  	return windows.IsConsole(fd)
   127  }
   128  
   129  // RestoreTerminal restores the terminal connected to the given file descriptor
   130  // to a previous state.
   131  func RestoreTerminal(fd uintptr, state *State) error {
   132  	return winterm.SetConsoleMode(fd, state.mode)
   133  }
   134  
   135  // SaveState saves the state of the terminal connected to the given file descriptor.
   136  func SaveState(fd uintptr) (*State, error) {
   137  	mode, e := winterm.GetConsoleMode(fd)
   138  	if e != nil {
   139  		return nil, e
   140  	}
   141  	return &State{mode}, nil
   142  }
   143  
   144  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   145  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   146  func DisableEcho(fd uintptr, state *State) error {
   147  	mode := state.mode
   148  	mode &^= winterm.ENABLE_ECHO_INPUT
   149  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   150  
   151  	err := winterm.SetConsoleMode(fd, mode)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	// Register an interrupt handler to catch and restore prior state
   157  	restoreAtInterrupt(fd, state)
   158  	return nil
   159  }
   160  
   161  // SetRawTerminal puts the terminal connected to the given file descriptor into raw
   162  // mode and returns the previous state.
   163  func SetRawTerminal(fd uintptr) (*State, error) {
   164  	state, err := MakeRaw(fd)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	// Register an interrupt handler to catch and restore prior state
   170  	restoreAtInterrupt(fd, state)
   171  	return state, err
   172  }
   173  
   174  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   175  // mode and returns the previous state of the terminal so that it can be restored.
   176  func MakeRaw(fd uintptr) (*State, error) {
   177  	state, err := SaveState(fd)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	// See
   183  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   184  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   185  	mode := state.mode
   186  
   187  	// Disable these modes
   188  	mode &^= winterm.ENABLE_ECHO_INPUT
   189  	mode &^= winterm.ENABLE_LINE_INPUT
   190  	mode &^= winterm.ENABLE_MOUSE_INPUT
   191  	mode &^= winterm.ENABLE_WINDOW_INPUT
   192  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   193  
   194  	// Enable these modes
   195  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   196  	mode |= winterm.ENABLE_INSERT_MODE
   197  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   198  
   199  	err = winterm.SetConsoleMode(fd, mode)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return state, nil
   204  }
   205  
   206  func restoreAtInterrupt(fd uintptr, state *State) {
   207  	sigchan := make(chan os.Signal, 1)
   208  	signal.Notify(sigchan, os.Interrupt)
   209  
   210  	go func() {
   211  		_ = <-sigchan
   212  		RestoreTerminal(fd, state)
   213  		os.Exit(0)
   214  	}()
   215  }