github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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/term/windows"
    13  )
    14  
    15  // State holds the console mode for the terminal.
    16  type State struct {
    17  	mode uint32
    18  }
    19  
    20  // Winsize is used for window size.
    21  type Winsize struct {
    22  	Height uint16
    23  	Width  uint16
    24  }
    25  
    26  const (
    27  	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
    28  	enableVirtualTerminalInput      = 0x0200
    29  	enableVirtualTerminalProcessing = 0x0004
    30  	disableNewlineAutoReturn        = 0x0008
    31  )
    32  
    33  // vtInputSupported is true if enableVirtualTerminalInput is supported by the console
    34  var vtInputSupported bool
    35  
    36  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    37  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    38  	// Turn on VT handling on all std handles, if possible. This might
    39  	// fail, in which case we will fall back to terminal emulation.
    40  	var emulateStdin, emulateStdout, emulateStderr bool
    41  	fd := os.Stdin.Fd()
    42  	if mode, err := winterm.GetConsoleMode(fd); err == nil {
    43  		// Validate that enableVirtualTerminalInput is supported, but do not set it.
    44  		if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil {
    45  			emulateStdin = true
    46  		} else {
    47  			vtInputSupported = true
    48  		}
    49  		// Unconditionally set the console mode back even on failure because SetConsoleMode
    50  		// remembers invalid bits on input handles.
    51  		winterm.SetConsoleMode(fd, mode)
    52  	}
    53  
    54  	fd = os.Stdout.Fd()
    55  	if mode, err := winterm.GetConsoleMode(fd); err == nil {
    56  		// Validate disableNewlineAutoReturn is supported, but do not set it.
    57  		if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
    58  			emulateStdout = true
    59  		} else {
    60  			winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
    61  		}
    62  	}
    63  
    64  	fd = os.Stderr.Fd()
    65  	if mode, err := winterm.GetConsoleMode(fd); err == nil {
    66  		// Validate disableNewlineAutoReturn is supported, but do not set it.
    67  		if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
    68  			emulateStderr = true
    69  		} else {
    70  			winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
    71  		}
    72  	}
    73  
    74  	if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" {
    75  		// The ConEmu and ConsoleZ terminals emulate ANSI on output streams well.
    76  		emulateStdin = true
    77  		emulateStdout = false
    78  		emulateStderr = false
    79  	}
    80  
    81  	if emulateStdin {
    82  		stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
    83  	} else {
    84  		stdIn = os.Stdin
    85  	}
    86  
    87  	if emulateStdout {
    88  		stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
    89  	} else {
    90  		stdOut = os.Stdout
    91  	}
    92  
    93  	if emulateStderr {
    94  		stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
    95  	} else {
    96  		stdErr = os.Stderr
    97  	}
    98  
    99  	return
   100  }
   101  
   102  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
   103  func GetFdInfo(in interface{}) (uintptr, bool) {
   104  	return windows.GetHandleInfo(in)
   105  }
   106  
   107  // GetWinsize returns the window size based on the specified file descriptor.
   108  func GetWinsize(fd uintptr) (*Winsize, error) {
   109  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	winsize := &Winsize{
   115  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
   116  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
   117  	}
   118  
   119  	return winsize, nil
   120  }
   121  
   122  // IsTerminal returns true if the given file descriptor is a terminal.
   123  func IsTerminal(fd uintptr) bool {
   124  	return windows.IsConsole(fd)
   125  }
   126  
   127  // RestoreTerminal restores the terminal connected to the given file descriptor
   128  // to a previous state.
   129  func RestoreTerminal(fd uintptr, state *State) error {
   130  	return winterm.SetConsoleMode(fd, state.mode)
   131  }
   132  
   133  // SaveState saves the state of the terminal connected to the given file descriptor.
   134  func SaveState(fd uintptr) (*State, error) {
   135  	mode, e := winterm.GetConsoleMode(fd)
   136  	if e != nil {
   137  		return nil, e
   138  	}
   139  
   140  	return &State{mode: mode}, nil
   141  }
   142  
   143  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   144  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   145  func DisableEcho(fd uintptr, state *State) error {
   146  	mode := state.mode
   147  	mode &^= winterm.ENABLE_ECHO_INPUT
   148  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   149  	err := winterm.SetConsoleMode(fd, mode)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	// Register an interrupt handler to catch and restore prior state
   155  	restoreAtInterrupt(fd, state)
   156  	return nil
   157  }
   158  
   159  // SetRawTerminal puts the terminal connected to the given file descriptor into
   160  // raw mode and returns the previous state. On UNIX, this puts both the input
   161  // and output into raw mode. On Windows, it only puts the input into raw mode.
   162  func SetRawTerminal(fd uintptr) (*State, error) {
   163  	state, err := MakeRaw(fd)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	// Register an interrupt handler to catch and restore prior state
   169  	restoreAtInterrupt(fd, state)
   170  	return state, err
   171  }
   172  
   173  // SetRawTerminalOutput puts the output of terminal connected to the given file
   174  // descriptor into raw mode. On UNIX, this does nothing and returns nil for the
   175  // state. On Windows, it disables LF -> CRLF translation.
   176  func SetRawTerminalOutput(fd uintptr) (*State, error) {
   177  	state, err := SaveState(fd)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	// Ignore failures, since disableNewlineAutoReturn might not be supported on this
   183  	// version of Windows.
   184  	winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
   185  	return state, err
   186  }
   187  
   188  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   189  // mode and returns the previous state of the terminal so that it can be restored.
   190  func MakeRaw(fd uintptr) (*State, error) {
   191  	state, err := SaveState(fd)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	mode := state.mode
   197  
   198  	// See
   199  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   200  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   201  
   202  	// Disable these modes
   203  	mode &^= winterm.ENABLE_ECHO_INPUT
   204  	mode &^= winterm.ENABLE_LINE_INPUT
   205  	mode &^= winterm.ENABLE_MOUSE_INPUT
   206  	mode &^= winterm.ENABLE_WINDOW_INPUT
   207  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   208  
   209  	// Enable these modes
   210  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   211  	mode |= winterm.ENABLE_INSERT_MODE
   212  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   213  	if vtInputSupported {
   214  		mode |= enableVirtualTerminalInput
   215  	}
   216  
   217  	err = winterm.SetConsoleMode(fd, mode)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	return state, nil
   222  }
   223  
   224  func restoreAtInterrupt(fd uintptr, state *State) {
   225  	sigchan := make(chan os.Signal, 1)
   226  	signal.Notify(sigchan, os.Interrupt)
   227  
   228  	go func() {
   229  		_ = <-sigchan
   230  		RestoreTerminal(fd, state)
   231  		os.Exit(0)
   232  	}()
   233  }