github.com/portworx/docker@v1.12.1/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" {
    75  		// The ConEmu terminal emulates ANSI on output streams well.
    76  		emulateStdout = false
    77  		emulateStderr = false
    78  	}
    79  
    80  	if emulateStdin {
    81  		stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
    82  	} else {
    83  		stdIn = os.Stdin
    84  	}
    85  
    86  	if emulateStdout {
    87  		stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
    88  	} else {
    89  		stdOut = os.Stdout
    90  	}
    91  
    92  	if emulateStderr {
    93  		stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
    94  	} else {
    95  		stdErr = os.Stderr
    96  	}
    97  
    98  	return
    99  }
   100  
   101  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
   102  func GetFdInfo(in interface{}) (uintptr, bool) {
   103  	return windows.GetHandleInfo(in)
   104  }
   105  
   106  // GetWinsize returns the window size based on the specified file descriptor.
   107  func GetWinsize(fd uintptr) (*Winsize, error) {
   108  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	winsize := &Winsize{
   114  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
   115  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
   116  	}
   117  
   118  	return winsize, nil
   119  }
   120  
   121  // IsTerminal returns true if the given file descriptor is a terminal.
   122  func IsTerminal(fd uintptr) bool {
   123  	return windows.IsConsole(fd)
   124  }
   125  
   126  // RestoreTerminal restores the terminal connected to the given file descriptor
   127  // to a previous state.
   128  func RestoreTerminal(fd uintptr, state *State) error {
   129  	return winterm.SetConsoleMode(fd, state.mode)
   130  }
   131  
   132  // SaveState saves the state of the terminal connected to the given file descriptor.
   133  func SaveState(fd uintptr) (*State, error) {
   134  	mode, e := winterm.GetConsoleMode(fd)
   135  	if e != nil {
   136  		return nil, e
   137  	}
   138  
   139  	return &State{mode: mode}, nil
   140  }
   141  
   142  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   143  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   144  func DisableEcho(fd uintptr, state *State) error {
   145  	mode := state.mode
   146  	mode &^= winterm.ENABLE_ECHO_INPUT
   147  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   148  	err := winterm.SetConsoleMode(fd, mode)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	// Register an interrupt handler to catch and restore prior state
   154  	restoreAtInterrupt(fd, state)
   155  	return nil
   156  }
   157  
   158  // SetRawTerminal puts the terminal connected to the given file descriptor into
   159  // raw mode and returns the previous state. On UNIX, this puts both the input
   160  // and output into raw mode. On Windows, it only puts the input into raw mode.
   161  func SetRawTerminal(fd uintptr) (*State, error) {
   162  	state, err := MakeRaw(fd)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	// Register an interrupt handler to catch and restore prior state
   168  	restoreAtInterrupt(fd, state)
   169  	return state, err
   170  }
   171  
   172  // SetRawTerminalOutput puts the output of terminal connected to the given file
   173  // descriptor into raw mode. On UNIX, this does nothing and returns nil for the
   174  // state. On Windows, it disables LF -> CRLF translation.
   175  func SetRawTerminalOutput(fd uintptr) (*State, error) {
   176  	state, err := SaveState(fd)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	// Ignore failures, since disableNewlineAutoReturn might not be supported on this
   182  	// version of Windows.
   183  	winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
   184  	return state, err
   185  }
   186  
   187  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   188  // mode and returns the previous state of the terminal so that it can be restored.
   189  func MakeRaw(fd uintptr) (*State, error) {
   190  	state, err := SaveState(fd)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	mode := state.mode
   196  
   197  	// See
   198  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   199  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   200  
   201  	// Disable these modes
   202  	mode &^= winterm.ENABLE_ECHO_INPUT
   203  	mode &^= winterm.ENABLE_LINE_INPUT
   204  	mode &^= winterm.ENABLE_MOUSE_INPUT
   205  	mode &^= winterm.ENABLE_WINDOW_INPUT
   206  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   207  
   208  	// Enable these modes
   209  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   210  	mode |= winterm.ENABLE_INSERT_MODE
   211  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   212  	if vtInputSupported {
   213  		mode |= enableVirtualTerminalInput
   214  	}
   215  
   216  	err = winterm.SetConsoleMode(fd, mode)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	return state, nil
   221  }
   222  
   223  func restoreAtInterrupt(fd uintptr, state *State) {
   224  	sigchan := make(chan os.Signal, 1)
   225  	signal.Notify(sigchan, os.Interrupt)
   226  
   227  	go func() {
   228  		_ = <-sigchan
   229  		RestoreTerminal(fd, state)
   230  		os.Exit(0)
   231  	}()
   232  }