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