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 }