github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/pkg/term/term_windows.go (about)

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