github.com/yamamoto-febc/docker@v1.9.0/pkg/term/term_windows.go (about)

     1  // +build windows
     2  
     3  package term
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/signal"
    10  
    11  	"github.com/Azure/go-ansiterm/winterm"
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/pkg/term/windows"
    14  )
    15  
    16  // State holds the console mode for the terminal.
    17  type State struct {
    18  	mode uint32
    19  }
    20  
    21  // Winsize is used for window size.
    22  type Winsize struct {
    23  	Height uint16
    24  	Width  uint16
    25  	x      uint16
    26  	y      uint16
    27  }
    28  
    29  // StdStreams returns the standard streams (stdin, stdout, stedrr).
    30  func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
    31  	switch {
    32  	case os.Getenv("ConEmuANSI") == "ON":
    33  		// The ConEmu shell emulates ANSI well by default.
    34  		return os.Stdin, os.Stdout, os.Stderr
    35  	case os.Getenv("MSYSTEM") != "":
    36  		// MSYS (mingw) does not emulate ANSI well.
    37  		return windows.ConsoleStreams()
    38  	default:
    39  		return windows.ConsoleStreams()
    40  	}
    41  }
    42  
    43  // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
    44  func GetFdInfo(in interface{}) (uintptr, bool) {
    45  	return windows.GetHandleInfo(in)
    46  }
    47  
    48  // GetWinsize returns the window size based on the specified file descriptor.
    49  func GetWinsize(fd uintptr) (*Winsize, error) {
    50  
    51  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	winsize := &Winsize{
    57  		Width:  uint16(info.Window.Right - info.Window.Left + 1),
    58  		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
    59  		x:      0,
    60  		y:      0}
    61  
    62  	// Note: GetWinsize is called frequently -- uncomment only for excessive details
    63  	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
    64  	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
    65  	return winsize, nil
    66  }
    67  
    68  // SetWinsize tries to set the specified window size for the specified file descriptor.
    69  func SetWinsize(fd uintptr, ws *Winsize) error {
    70  
    71  	// Ensure the requested dimensions are no larger than the maximum window size
    72  	info, err := winterm.GetConsoleScreenBufferInfo(fd)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) {
    78  		return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)",
    79  			ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y)
    80  	}
    81  
    82  	// Narrow the sizes to that used by Windows
    83  	width := winterm.SHORT(ws.Width)
    84  	height := winterm.SHORT(ws.Height)
    85  
    86  	// Set the dimensions while ensuring they remain within the bounds of the backing console buffer
    87  	// -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs,
    88  	//    shift the upper left just enough to keep the new window within the buffer.
    89  	rect := info.Window
    90  	if width < rect.Right-rect.Left+1 {
    91  		rect.Right = rect.Left + width - 1
    92  	} else if width > rect.Right-rect.Left+1 {
    93  		rect.Right = rect.Left + width - 1
    94  		if rect.Right >= info.Size.X {
    95  			rect.Left = info.Size.X - width
    96  			rect.Right = info.Size.X - 1
    97  		}
    98  	}
    99  
   100  	if height < rect.Bottom-rect.Top+1 {
   101  		rect.Bottom = rect.Top + height - 1
   102  	} else if height > rect.Bottom-rect.Top+1 {
   103  		rect.Bottom = rect.Top + height - 1
   104  		if rect.Bottom >= info.Size.Y {
   105  			rect.Top = info.Size.Y - height
   106  			rect.Bottom = info.Size.Y - 1
   107  		}
   108  	}
   109  	logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect)
   110  
   111  	return winterm.SetConsoleWindowInfo(fd, true, rect)
   112  }
   113  
   114  // IsTerminal returns true if the given file descriptor is a terminal.
   115  func IsTerminal(fd uintptr) bool {
   116  	return windows.IsConsole(fd)
   117  }
   118  
   119  // RestoreTerminal restores the terminal connected to the given file descriptor
   120  // to a previous state.
   121  func RestoreTerminal(fd uintptr, state *State) error {
   122  	return winterm.SetConsoleMode(fd, state.mode)
   123  }
   124  
   125  // SaveState saves the state of the terminal connected to the given file descriptor.
   126  func SaveState(fd uintptr) (*State, error) {
   127  	mode, e := winterm.GetConsoleMode(fd)
   128  	if e != nil {
   129  		return nil, e
   130  	}
   131  	return &State{mode}, nil
   132  }
   133  
   134  // DisableEcho disables echo for the terminal connected to the given file descriptor.
   135  // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   136  func DisableEcho(fd uintptr, state *State) error {
   137  	mode := state.mode
   138  	mode &^= winterm.ENABLE_ECHO_INPUT
   139  	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
   140  
   141  	err := winterm.SetConsoleMode(fd, mode)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	// Register an interrupt handler to catch and restore prior state
   147  	restoreAtInterrupt(fd, state)
   148  	return nil
   149  }
   150  
   151  // SetRawTerminal puts the terminal connected to the given file descriptor into raw
   152  // mode and returns the previous state.
   153  func SetRawTerminal(fd uintptr) (*State, error) {
   154  	state, err := MakeRaw(fd)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	// Register an interrupt handler to catch and restore prior state
   160  	restoreAtInterrupt(fd, state)
   161  	return state, err
   162  }
   163  
   164  // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
   165  // mode and returns the previous state of the terminal so that it can be restored.
   166  func MakeRaw(fd uintptr) (*State, error) {
   167  	state, err := SaveState(fd)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	// See
   173  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
   174  	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
   175  	mode := state.mode
   176  
   177  	// Disable these modes
   178  	mode &^= winterm.ENABLE_ECHO_INPUT
   179  	mode &^= winterm.ENABLE_LINE_INPUT
   180  	mode &^= winterm.ENABLE_MOUSE_INPUT
   181  	mode &^= winterm.ENABLE_WINDOW_INPUT
   182  	mode &^= winterm.ENABLE_PROCESSED_INPUT
   183  
   184  	// Enable these modes
   185  	mode |= winterm.ENABLE_EXTENDED_FLAGS
   186  	mode |= winterm.ENABLE_INSERT_MODE
   187  	mode |= winterm.ENABLE_QUICK_EDIT_MODE
   188  
   189  	err = winterm.SetConsoleMode(fd, mode)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	return state, nil
   194  }
   195  
   196  func restoreAtInterrupt(fd uintptr, state *State) {
   197  	sigchan := make(chan os.Signal, 1)
   198  	signal.Notify(sigchan, os.Interrupt)
   199  
   200  	go func() {
   201  		_ = <-sigchan
   202  		RestoreTerminal(fd, state)
   203  		os.Exit(0)
   204  	}()
   205  }