github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/pkg/term/term_windows.go (about) 1 // +build windows 2 3 package term 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "os/signal" 10 "syscall" 11 12 "github.com/Azure/go-ansiterm/winterm" 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/pkg/system" 15 "github.com/docker/docker/pkg/term/windows" 16 ) 17 18 // State holds the console mode for the terminal. 19 type State struct { 20 mode uint32 21 } 22 23 // Winsize is used for window size. 24 type Winsize struct { 25 Height uint16 26 Width uint16 27 x uint16 28 y uint16 29 } 30 31 // StdStreams returns the standard streams (stdin, stdout, stedrr). 32 func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { 33 switch { 34 case os.Getenv("ConEmuANSI") == "ON": 35 // The ConEmu shell emulates ANSI well by default. 36 return os.Stdin, os.Stdout, os.Stderr 37 case os.Getenv("MSYSTEM") != "": 38 // MSYS (mingw) does not emulate ANSI well. 39 return windows.ConsoleStreams() 40 default: 41 if useNativeConsole() { 42 return os.Stdin, os.Stdout, os.Stderr 43 } 44 return windows.ConsoleStreams() 45 } 46 } 47 48 // useNativeConsole determines if the docker client should use the built-in 49 // console which supports ANSI emulation, or fall-back to the golang emulator 50 // (github.com/azure/go-ansiterm). 51 func useNativeConsole() bool { 52 osv, err := system.GetOSVersion() 53 if err != nil { 54 return false 55 } 56 57 // Native console is not available major version 10 58 if osv.MajorVersion < 10 { 59 return false 60 } 61 62 // Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later 63 if osv.Build < 10578 { 64 return false 65 } 66 67 // Environment variable override 68 if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" { 69 if e == "1" { 70 return true 71 } 72 return false 73 } 74 75 // Get the handle to stdout 76 stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE) 77 if err != nil { 78 return false 79 } 80 81 // Get the console mode from the consoles stdout handle 82 var mode uint32 83 if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil { 84 return false 85 } 86 87 // Legacy mode does not have native ANSI emulation. 88 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx 89 const enableVirtualTerminalProcessing = 0x0004 90 if mode&enableVirtualTerminalProcessing == 0 { 91 return false 92 } 93 94 // TODO Windows (Post TP4). The native emulator still has issues which 95 // mean it shouldn't be enabled for everyone. Change this next line to true 96 // to change the default to "enable if available". In the meantime, users 97 // can still try it out by using USE_NATIVE_CONSOLE env variable. 98 return false 99 } 100 101 // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. 102 func GetFdInfo(in interface{}) (uintptr, bool) { 103 return windows.GetHandleInfo(in) 104 } 105 106 // GetWinsize returns the window size based on the specified file descriptor. 107 func GetWinsize(fd uintptr) (*Winsize, error) { 108 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 x: 0, 118 y: 0} 119 120 // Note: GetWinsize is called frequently -- uncomment only for excessive details 121 // logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String()) 122 // logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y) 123 return winsize, nil 124 } 125 126 // SetWinsize tries to set the specified window size for the specified file descriptor. 127 func SetWinsize(fd uintptr, ws *Winsize) error { 128 129 // Ensure the requested dimensions are no larger than the maximum window size 130 info, err := winterm.GetConsoleScreenBufferInfo(fd) 131 if err != nil { 132 return err 133 } 134 135 if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) { 136 return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)", 137 ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y) 138 } 139 140 // Narrow the sizes to that used by Windows 141 width := winterm.SHORT(ws.Width) 142 height := winterm.SHORT(ws.Height) 143 144 // Set the dimensions while ensuring they remain within the bounds of the backing console buffer 145 // -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs, 146 // shift the upper left just enough to keep the new window within the buffer. 147 rect := info.Window 148 if width < rect.Right-rect.Left+1 { 149 rect.Right = rect.Left + width - 1 150 } else if width > rect.Right-rect.Left+1 { 151 rect.Right = rect.Left + width - 1 152 if rect.Right >= info.Size.X { 153 rect.Left = info.Size.X - width 154 rect.Right = info.Size.X - 1 155 } 156 } 157 158 if height < rect.Bottom-rect.Top+1 { 159 rect.Bottom = rect.Top + height - 1 160 } else if height > rect.Bottom-rect.Top+1 { 161 rect.Bottom = rect.Top + height - 1 162 if rect.Bottom >= info.Size.Y { 163 rect.Top = info.Size.Y - height 164 rect.Bottom = info.Size.Y - 1 165 } 166 } 167 logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect) 168 169 return winterm.SetConsoleWindowInfo(fd, true, rect) 170 } 171 172 // IsTerminal returns true if the given file descriptor is a terminal. 173 func IsTerminal(fd uintptr) bool { 174 return windows.IsConsole(fd) 175 } 176 177 // RestoreTerminal restores the terminal connected to the given file descriptor 178 // to a previous state. 179 func RestoreTerminal(fd uintptr, state *State) error { 180 return winterm.SetConsoleMode(fd, state.mode) 181 } 182 183 // SaveState saves the state of the terminal connected to the given file descriptor. 184 func SaveState(fd uintptr) (*State, error) { 185 mode, e := winterm.GetConsoleMode(fd) 186 if e != nil { 187 return nil, e 188 } 189 return &State{mode}, nil 190 } 191 192 // DisableEcho disables echo for the terminal connected to the given file descriptor. 193 // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 194 func DisableEcho(fd uintptr, state *State) error { 195 mode := state.mode 196 mode &^= winterm.ENABLE_ECHO_INPUT 197 mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT 198 199 err := winterm.SetConsoleMode(fd, mode) 200 if err != nil { 201 return err 202 } 203 204 // Register an interrupt handler to catch and restore prior state 205 restoreAtInterrupt(fd, state) 206 return nil 207 } 208 209 // SetRawTerminal puts the terminal connected to the given file descriptor into raw 210 // mode and returns the previous state. 211 func SetRawTerminal(fd uintptr) (*State, error) { 212 state, err := MakeRaw(fd) 213 if err != nil { 214 return nil, err 215 } 216 217 // Register an interrupt handler to catch and restore prior state 218 restoreAtInterrupt(fd, state) 219 return state, err 220 } 221 222 // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw 223 // mode and returns the previous state of the terminal so that it can be restored. 224 func MakeRaw(fd uintptr) (*State, error) { 225 state, err := SaveState(fd) 226 if err != nil { 227 return nil, err 228 } 229 230 // See 231 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx 232 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 233 mode := state.mode 234 235 // Disable these modes 236 mode &^= winterm.ENABLE_ECHO_INPUT 237 mode &^= winterm.ENABLE_LINE_INPUT 238 mode &^= winterm.ENABLE_MOUSE_INPUT 239 mode &^= winterm.ENABLE_WINDOW_INPUT 240 mode &^= winterm.ENABLE_PROCESSED_INPUT 241 242 // Enable these modes 243 mode |= winterm.ENABLE_EXTENDED_FLAGS 244 mode |= winterm.ENABLE_INSERT_MODE 245 mode |= winterm.ENABLE_QUICK_EDIT_MODE 246 247 err = winterm.SetConsoleMode(fd, mode) 248 if err != nil { 249 return nil, err 250 } 251 return state, nil 252 } 253 254 func restoreAtInterrupt(fd uintptr, state *State) { 255 sigchan := make(chan os.Signal, 1) 256 signal.Notify(sigchan, os.Interrupt) 257 258 go func() { 259 _ = <-sigchan 260 RestoreTerminal(fd, state) 261 os.Exit(0) 262 }() 263 }