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