github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/pkg/term/term_windows.go (about)

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