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