github.com/uppal0016/docker_new@v0.0.0-20240123060250-1c98be13ac2c/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/system"
    13  	"github.com/docker/docker/pkg/term/windows"
    14  )
    15  
    16  // State holds the console mode for the terminal.
    17  type State struct {
    18  	inMode, outMode     uint32
    19  	inHandle, outHandle syscall.Handle
    20  }
    21  
    22  // Winsize is used for window size.
    23  type Winsize struct {
    24  	Height uint16
    25  	Width  uint16
    26  }
    27  
    28  const (
    29  	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
    30  	enableVirtualTerminalInput      = 0x0200
    31  	enableVirtualTerminalProcessing = 0x0004
    32  	disableNewlineAutoReturn        = 0x0008
    33  )
    34  
    35  // usingNativeConsole is true if we are using the Windows native console
    36  var usingNativeConsole bool
    37  
    38  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    39  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    40  	switch {
    41  	case os.Getenv("ConEmuANSI") == "ON":
    42  		// The ConEmu terminal emulates ANSI on output streams well.
    43  		return windows.ConEmuStreams()
    44  	case os.Getenv("MSYSTEM") != "":
    45  		// MSYS (mingw) does not emulate ANSI well.
    46  		return windows.ConsoleStreams()
    47  	default:
    48  		if useNativeConsole() {
    49  			usingNativeConsole = true
    50  			return os.Stdin, os.Stdout, os.Stderr
    51  		}
    52  		return windows.ConsoleStreams()
    53  	}
    54  }
    55  
    56  // useNativeConsole determines if the docker client should use the built-in
    57  // console which supports ANSI emulation, or fall-back to the golang emulator
    58  // (github.com/azure/go-ansiterm).
    59  func useNativeConsole() bool {
    60  	osv := system.GetOSVersion()
    61  
    62  	// Native console is not available before major version 10
    63  	if osv.MajorVersion < 10 {
    64  		return false
    65  	}
    66  
    67  	// Get the console modes. If this fails, we can't use the native console
    68  	state, err := getNativeConsole()
    69  	if err != nil {
    70  		return false
    71  	}
    72  
    73  	// Probe the console to see if it can be enabled.
    74  	if nil != probeNativeConsole(state) {
    75  		return false
    76  	}
    77  
    78  	// Environment variable override
    79  	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
    80  		if e == "1" {
    81  			return true
    82  		}
    83  		return false
    84  	}
    85  
    86  	// TODO Windows. The native emulator still has issues which
    87  	// mean it shouldn't be enabled for everyone. Change this next line to true
    88  	// to change the default to "enable if available". In the meantime, users
    89  	// can still try it out by using USE_NATIVE_CONSOLE env variable.
    90  	return false
    91  }
    92  
    93  // getNativeConsole returns the console modes ('state') for the native Windows console
    94  func getNativeConsole() (State, error) {
    95  	var (
    96  		err   error
    97  		state State
    98  	)
    99  
   100  	// Get the handle to stdout
   101  	if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
   102  		return state, err
   103  	}
   104  
   105  	// Get the console mode from the consoles stdout handle
   106  	if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
   107  		return state, err
   108  	}
   109  
   110  	// Get the handle to stdin
   111  	if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
   112  		return state, err
   113  	}
   114  
   115  	// Get the console mode from the consoles stdin handle
   116  	if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
   117  		return state, err
   118  	}
   119  
   120  	return state, nil
   121  }
   122  
   123  // probeNativeConsole probes the console to determine if native can be supported,
   124  func probeNativeConsole(state State) error {
   125  	if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
   126  		return err
   127  	}
   128  	defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
   129  
   130  	if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
   131  		return err
   132  	}
   133  	defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
   134  
   135  	return nil
   136  }
   137  
   138  // enableNativeConsole turns on native console mode
   139  func enableNativeConsole(state State) error {
   140  	// First attempt both enableVirtualTerminalProcessing and disableNewlineAutoReturn
   141  	if err := winterm.SetConsoleMode(uintptr(state.outHandle),
   142  		state.outMode|(enableVirtualTerminalProcessing|disableNewlineAutoReturn)); err != nil {
   143  
   144  		// That may fail, so fallback to trying just enableVirtualTerminalProcessing
   145  		if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
   151  		winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can
   152  		return err
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  // disableNativeConsole turns off native console mode
   159  func disableNativeConsole(state *State) error {
   160  	// Try and restore both in an out before error checking.
   161  	errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
   162  	errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
   163  	if errout != nil {
   164  		return errout
   165  	}
   166  	if errin != nil {
   167  		return errin
   168  	}
   169  	return nil
   170  }
   171  
   172  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
   173  func GetFdInfo(in interface{}) (uintptr, bool) {
   174  	return windows.GetHandleInfo(in)
   175  }
   176  
   177  // GetWinsize returns the window size based on the specified file descriptor.
   178  func GetWinsize(fd uintptr) (*Winsize, error) {
   179  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	winsize := &Winsize{
   185  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
   186  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
   187  	}
   188  
   189  	return winsize, nil
   190  }
   191  
   192  // IsTerminal returns true if the given file descriptor is a terminal.
   193  func IsTerminal(fd uintptr) bool {
   194  	return windows.IsConsole(fd)
   195  }
   196  
   197  // RestoreTerminal restores the terminal connected to the given file descriptor
   198  // to a previous state.
   199  func RestoreTerminal(fd uintptr, state *State) error {
   200  	if usingNativeConsole {
   201  		return disableNativeConsole(state)
   202  	}
   203  	return winterm.SetConsoleMode(fd, state.outMode)
   204  }
   205  
   206  // SaveState saves the state of the terminal connected to the given file descriptor.
   207  func SaveState(fd uintptr) (*State, error) {
   208  	if usingNativeConsole {
   209  		state, err := getNativeConsole()
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		return &state, nil
   214  	}
   215  
   216  	mode, e := winterm.GetConsoleMode(fd)
   217  	if e != nil {
   218  		return nil, e
   219  	}
   220  
   221  	return &State{outMode: mode}, nil
   222  }
   223  
   224  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   225  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   226  func DisableEcho(fd uintptr, state *State) error {
   227  	mode := state.inMode
   228  	mode &^= winterm.ENABLE_ECHO_INPUT
   229  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   230  	err := winterm.SetConsoleMode(fd, mode)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	// Register an interrupt handler to catch and restore prior state
   236  	restoreAtInterrupt(fd, state)
   237  	return nil
   238  }
   239  
   240  // SetRawTerminal puts the terminal connected to the given file descriptor into raw
   241  // mode and returns the previous state.
   242  func SetRawTerminal(fd uintptr) (*State, error) {
   243  	state, err := MakeRaw(fd)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	// Register an interrupt handler to catch and restore prior state
   249  	restoreAtInterrupt(fd, state)
   250  	return state, err
   251  }
   252  
   253  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   254  // mode and returns the previous state of the terminal so that it can be restored.
   255  func MakeRaw(fd uintptr) (*State, error) {
   256  	state, err := SaveState(fd)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	mode := state.inMode
   262  	if usingNativeConsole {
   263  		if err := enableNativeConsole(*state); err != nil {
   264  			return nil, err
   265  		}
   266  		mode |= enableVirtualTerminalInput
   267  	}
   268  
   269  	// See
   270  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   271  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   272  
   273  	// Disable these modes
   274  	mode &^= winterm.ENABLE_ECHO_INPUT
   275  	mode &^= winterm.ENABLE_LINE_INPUT
   276  	mode &^= winterm.ENABLE_MOUSE_INPUT
   277  	mode &^= winterm.ENABLE_WINDOW_INPUT
   278  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   279  
   280  	// Enable these modes
   281  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   282  	mode |= winterm.ENABLE_INSERT_MODE
   283  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   284  
   285  	err = winterm.SetConsoleMode(fd, mode)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  	return state, nil
   290  }
   291  
   292  func restoreAtInterrupt(fd uintptr, state *State) {
   293  	sigchan := make(chan os.Signal, 1)
   294  	signal.Notify(sigchan, os.Interrupt)
   295  
   296  	go func() {
   297  		_ = <-sigchan
   298  		RestoreTerminal(fd, state)
   299  		os.Exit(0)
   300  	}()
   301  }