github.com/kunnos/engine@v1.13.1/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/term/windows" 13 ) 14 15 // State holds the console mode for the terminal. 16 type State struct { 17 mode uint32 18 } 19 20 // Winsize is used for window size. 21 type Winsize struct { 22 Height uint16 23 Width uint16 24 } 25 26 const ( 27 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx 28 enableVirtualTerminalInput = 0x0200 29 enableVirtualTerminalProcessing = 0x0004 30 disableNewlineAutoReturn = 0x0008 31 ) 32 33 // vtInputSupported is true if enableVirtualTerminalInput is supported by the console 34 var vtInputSupported bool 35 36 // StdStreams returns the standard streams (stdin, stdout, stedrr). 37 func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { 38 // Turn on VT handling on all std handles, if possible. This might 39 // fail, in which case we will fall back to terminal emulation. 40 var emulateStdin, emulateStdout, emulateStderr bool 41 fd := os.Stdin.Fd() 42 if mode, err := winterm.GetConsoleMode(fd); err == nil { 43 // Validate that enableVirtualTerminalInput is supported, but do not set it. 44 if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil { 45 emulateStdin = true 46 } else { 47 vtInputSupported = true 48 } 49 // Unconditionally set the console mode back even on failure because SetConsoleMode 50 // remembers invalid bits on input handles. 51 winterm.SetConsoleMode(fd, mode) 52 } 53 54 fd = os.Stdout.Fd() 55 if mode, err := winterm.GetConsoleMode(fd); err == nil { 56 // Validate disableNewlineAutoReturn is supported, but do not set it. 57 if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil { 58 emulateStdout = true 59 } else { 60 winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing) 61 } 62 } 63 64 fd = os.Stderr.Fd() 65 if mode, err := winterm.GetConsoleMode(fd); err == nil { 66 // Validate disableNewlineAutoReturn is supported, but do not set it. 67 if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil { 68 emulateStderr = true 69 } else { 70 winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing) 71 } 72 } 73 74 if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" { 75 // The ConEmu and ConsoleZ terminals emulate ANSI on output streams well. 76 emulateStdin = true 77 emulateStdout = false 78 emulateStderr = false 79 } 80 81 if emulateStdin { 82 stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE) 83 } else { 84 stdIn = os.Stdin 85 } 86 87 if emulateStdout { 88 stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE) 89 } else { 90 stdOut = os.Stdout 91 } 92 93 if emulateStderr { 94 stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE) 95 } else { 96 stdErr = os.Stderr 97 } 98 99 return 100 } 101 102 // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. 103 func GetFdInfo(in interface{}) (uintptr, bool) { 104 return windows.GetHandleInfo(in) 105 } 106 107 // GetWinsize returns the window size based on the specified file descriptor. 108 func GetWinsize(fd uintptr) (*Winsize, error) { 109 info, err := winterm.GetConsoleScreenBufferInfo(fd) 110 if err != nil { 111 return nil, err 112 } 113 114 winsize := &Winsize{ 115 Width: uint16(info.Window.Right - info.Window.Left + 1), 116 Height: uint16(info.Window.Bottom - info.Window.Top + 1), 117 } 118 119 return winsize, nil 120 } 121 122 // IsTerminal returns true if the given file descriptor is a terminal. 123 func IsTerminal(fd uintptr) bool { 124 return windows.IsConsole(fd) 125 } 126 127 // RestoreTerminal restores the terminal connected to the given file descriptor 128 // to a previous state. 129 func RestoreTerminal(fd uintptr, state *State) error { 130 return winterm.SetConsoleMode(fd, state.mode) 131 } 132 133 // SaveState saves the state of the terminal connected to the given file descriptor. 134 func SaveState(fd uintptr) (*State, error) { 135 mode, e := winterm.GetConsoleMode(fd) 136 if e != nil { 137 return nil, e 138 } 139 140 return &State{mode: mode}, nil 141 } 142 143 // DisableEcho disables echo for the terminal connected to the given file descriptor. 144 // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 145 func DisableEcho(fd uintptr, state *State) error { 146 mode := state.mode 147 mode &^= winterm.ENABLE_ECHO_INPUT 148 mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT 149 err := winterm.SetConsoleMode(fd, mode) 150 if err != nil { 151 return err 152 } 153 154 // Register an interrupt handler to catch and restore prior state 155 restoreAtInterrupt(fd, state) 156 return nil 157 } 158 159 // SetRawTerminal puts the terminal connected to the given file descriptor into 160 // raw mode and returns the previous state. On UNIX, this puts both the input 161 // and output into raw mode. On Windows, it only puts the input into raw mode. 162 func SetRawTerminal(fd uintptr) (*State, error) { 163 state, err := MakeRaw(fd) 164 if err != nil { 165 return nil, err 166 } 167 168 // Register an interrupt handler to catch and restore prior state 169 restoreAtInterrupt(fd, state) 170 return state, err 171 } 172 173 // SetRawTerminalOutput puts the output of terminal connected to the given file 174 // descriptor into raw mode. On UNIX, this does nothing and returns nil for the 175 // state. On Windows, it disables LF -> CRLF translation. 176 func SetRawTerminalOutput(fd uintptr) (*State, error) { 177 state, err := SaveState(fd) 178 if err != nil { 179 return nil, err 180 } 181 182 // Ignore failures, since disableNewlineAutoReturn might not be supported on this 183 // version of Windows. 184 winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn) 185 return state, err 186 } 187 188 // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw 189 // mode and returns the previous state of the terminal so that it can be restored. 190 func MakeRaw(fd uintptr) (*State, error) { 191 state, err := SaveState(fd) 192 if err != nil { 193 return nil, err 194 } 195 196 mode := state.mode 197 198 // See 199 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx 200 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 201 202 // Disable these modes 203 mode &^= winterm.ENABLE_ECHO_INPUT 204 mode &^= winterm.ENABLE_LINE_INPUT 205 mode &^= winterm.ENABLE_MOUSE_INPUT 206 mode &^= winterm.ENABLE_WINDOW_INPUT 207 mode &^= winterm.ENABLE_PROCESSED_INPUT 208 209 // Enable these modes 210 mode |= winterm.ENABLE_EXTENDED_FLAGS 211 mode |= winterm.ENABLE_INSERT_MODE 212 mode |= winterm.ENABLE_QUICK_EDIT_MODE 213 if vtInputSupported { 214 mode |= enableVirtualTerminalInput 215 } 216 217 err = winterm.SetConsoleMode(fd, mode) 218 if err != nil { 219 return nil, err 220 } 221 return state, nil 222 } 223 224 func restoreAtInterrupt(fd uintptr, state *State) { 225 sigchan := make(chan os.Signal, 1) 226 signal.Notify(sigchan, os.Interrupt) 227 228 go func() { 229 _ = <-sigchan 230 RestoreTerminal(fd, state) 231 os.Exit(0) 232 }() 233 }