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 }