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