github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/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 // Must have a post-TP5 RS1 build of Windows Server 2016/Windows 10 for 87 // the native console to be usable. 88 if osv.Build < 14350 { 89 return false 90 } 91 92 return true 93 } 94 95 // getNativeConsole returns the console modes ('state') for the native Windows console 96 func getNativeConsole() (State, error) { 97 var ( 98 err error 99 state State 100 ) 101 102 // Get the handle to stdout 103 if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil { 104 return state, err 105 } 106 107 // Get the console mode from the consoles stdout handle 108 if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil { 109 return state, err 110 } 111 112 // Get the handle to stdin 113 if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil { 114 return state, err 115 } 116 117 // Get the console mode from the consoles stdin handle 118 if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil { 119 return state, err 120 } 121 122 return state, nil 123 } 124 125 // probeNativeConsole probes the console to determine if native can be supported, 126 func probeNativeConsole(state State) error { 127 if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil { 128 return err 129 } 130 defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) 131 132 if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil { 133 return err 134 } 135 defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode) 136 137 return nil 138 } 139 140 // enableNativeConsole turns on native console mode 141 func enableNativeConsole(state State) error { 142 // First attempt both enableVirtualTerminalProcessing and disableNewlineAutoReturn 143 if err := winterm.SetConsoleMode(uintptr(state.outHandle), 144 state.outMode|(enableVirtualTerminalProcessing|disableNewlineAutoReturn)); err != nil { 145 146 // That may fail, so fallback to trying just enableVirtualTerminalProcessing 147 if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil { 148 return err 149 } 150 } 151 152 if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil { 153 winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can 154 return err 155 } 156 157 return nil 158 } 159 160 // disableNativeConsole turns off native console mode 161 func disableNativeConsole(state *State) error { 162 // Try and restore both in an out before error checking. 163 errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) 164 errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode) 165 if errout != nil { 166 return errout 167 } 168 if errin != nil { 169 return errin 170 } 171 return nil 172 } 173 174 // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. 175 func GetFdInfo(in interface{}) (uintptr, bool) { 176 return windows.GetHandleInfo(in) 177 } 178 179 // GetWinsize returns the window size based on the specified file descriptor. 180 func GetWinsize(fd uintptr) (*Winsize, error) { 181 info, err := winterm.GetConsoleScreenBufferInfo(fd) 182 if err != nil { 183 return nil, err 184 } 185 186 winsize := &Winsize{ 187 Width: uint16(info.Window.Right - info.Window.Left + 1), 188 Height: uint16(info.Window.Bottom - info.Window.Top + 1), 189 } 190 191 return winsize, nil 192 } 193 194 // IsTerminal returns true if the given file descriptor is a terminal. 195 func IsTerminal(fd uintptr) bool { 196 return windows.IsConsole(fd) 197 } 198 199 // RestoreTerminal restores the terminal connected to the given file descriptor 200 // to a previous state. 201 func RestoreTerminal(fd uintptr, state *State) error { 202 if usingNativeConsole { 203 return disableNativeConsole(state) 204 } 205 return winterm.SetConsoleMode(fd, state.outMode) 206 } 207 208 // SaveState saves the state of the terminal connected to the given file descriptor. 209 func SaveState(fd uintptr) (*State, error) { 210 if usingNativeConsole { 211 state, err := getNativeConsole() 212 if err != nil { 213 return nil, err 214 } 215 return &state, nil 216 } 217 218 mode, e := winterm.GetConsoleMode(fd) 219 if e != nil { 220 return nil, e 221 } 222 223 return &State{outMode: mode}, nil 224 } 225 226 // DisableEcho disables echo for the terminal connected to the given file descriptor. 227 // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 228 func DisableEcho(fd uintptr, state *State) error { 229 mode := state.inMode 230 mode &^= winterm.ENABLE_ECHO_INPUT 231 mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT 232 err := winterm.SetConsoleMode(fd, mode) 233 if err != nil { 234 return err 235 } 236 237 // Register an interrupt handler to catch and restore prior state 238 restoreAtInterrupt(fd, state) 239 return nil 240 } 241 242 // SetRawTerminal puts the terminal connected to the given file descriptor into raw 243 // mode and returns the previous state. 244 func SetRawTerminal(fd uintptr) (*State, error) { 245 state, err := MakeRaw(fd) 246 if err != nil { 247 return nil, err 248 } 249 250 // Register an interrupt handler to catch and restore prior state 251 restoreAtInterrupt(fd, state) 252 return state, err 253 } 254 255 // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw 256 // mode and returns the previous state of the terminal so that it can be restored. 257 func MakeRaw(fd uintptr) (*State, error) { 258 state, err := SaveState(fd) 259 if err != nil { 260 return nil, err 261 } 262 263 mode := state.inMode 264 if usingNativeConsole { 265 if err := enableNativeConsole(*state); err != nil { 266 return nil, err 267 } 268 mode |= enableVirtualTerminalInput 269 } 270 271 // See 272 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx 273 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 274 275 // Disable these modes 276 mode &^= winterm.ENABLE_ECHO_INPUT 277 mode &^= winterm.ENABLE_LINE_INPUT 278 mode &^= winterm.ENABLE_MOUSE_INPUT 279 mode &^= winterm.ENABLE_WINDOW_INPUT 280 mode &^= winterm.ENABLE_PROCESSED_INPUT 281 282 // Enable these modes 283 mode |= winterm.ENABLE_EXTENDED_FLAGS 284 mode |= winterm.ENABLE_INSERT_MODE 285 mode |= winterm.ENABLE_QUICK_EDIT_MODE 286 287 err = winterm.SetConsoleMode(fd, mode) 288 if err != nil { 289 return nil, err 290 } 291 return state, nil 292 } 293 294 func restoreAtInterrupt(fd uintptr, state *State) { 295 sigchan := make(chan os.Signal, 1) 296 signal.Notify(sigchan, os.Interrupt) 297 298 go func() { 299 _ = <-sigchan 300 RestoreTerminal(fd, state) 301 os.Exit(0) 302 }() 303 }