github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/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 mode uint32 19 } 20 21 // Winsize is used for window size. 22 type Winsize struct { 23 Height uint16 24 Width uint16 25 x uint16 26 y uint16 27 } 28 29 // StdStreams returns the standard streams (stdin, stdout, stedrr). 30 func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { 31 switch { 32 case os.Getenv("ConEmuANSI") == "ON": 33 // The ConEmu shell emulates ANSI well by default. 34 return os.Stdin, os.Stdout, os.Stderr 35 case os.Getenv("MSYSTEM") != "": 36 // MSYS (mingw) does not emulate ANSI well. 37 return windows.ConsoleStreams() 38 default: 39 if useNativeConsole() { 40 return os.Stdin, os.Stdout, os.Stderr 41 } 42 return windows.ConsoleStreams() 43 } 44 } 45 46 // useNativeConsole determines if the docker client should use the built-in 47 // console which supports ANSI emulation, or fall-back to the golang emulator 48 // (github.com/azure/go-ansiterm). 49 func useNativeConsole() bool { 50 osv, err := system.GetOSVersion() 51 if err != nil { 52 return false 53 } 54 55 // Native console is not available major version 10 56 if osv.MajorVersion < 10 { 57 return false 58 } 59 60 // Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later 61 if osv.Build < 10578 { 62 return false 63 } 64 65 // Environment variable override 66 if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" { 67 if e == "1" { 68 return true 69 } 70 return false 71 } 72 73 // Get the handle to stdout 74 stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE) 75 if err != nil { 76 return false 77 } 78 79 // Get the console mode from the consoles stdout handle 80 var mode uint32 81 if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil { 82 return false 83 } 84 85 // Legacy mode does not have native ANSI emulation. 86 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx 87 const enableVirtualTerminalProcessing = 0x0004 88 if mode&enableVirtualTerminalProcessing == 0 { 89 return false 90 } 91 92 // TODO Windows (Post TP4). The native emulator still has issues which 93 // mean it shouldn't be enabled for everyone. Change this next line to true 94 // to change the default to "enable if available". In the meantime, users 95 // can still try it out by using USE_NATIVE_CONSOLE env variable. 96 return false 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 windows.GetHandleInfo(in) 102 } 103 104 // GetWinsize returns the window size based on the specified file descriptor. 105 func GetWinsize(fd uintptr) (*Winsize, error) { 106 107 info, err := winterm.GetConsoleScreenBufferInfo(fd) 108 if err != nil { 109 return nil, err 110 } 111 112 winsize := &Winsize{ 113 Width: uint16(info.Window.Right - info.Window.Left + 1), 114 Height: uint16(info.Window.Bottom - info.Window.Top + 1), 115 x: 0, 116 y: 0} 117 118 // Note: GetWinsize is called frequently -- uncomment only for excessive details 119 // logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String()) 120 // logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y) 121 return winsize, nil 122 } 123 124 // IsTerminal returns true if the given file descriptor is a terminal. 125 func IsTerminal(fd uintptr) bool { 126 return windows.IsConsole(fd) 127 } 128 129 // RestoreTerminal restores the terminal connected to the given file descriptor 130 // to a previous state. 131 func RestoreTerminal(fd uintptr, state *State) error { 132 return winterm.SetConsoleMode(fd, state.mode) 133 } 134 135 // SaveState saves the state of the terminal connected to the given file descriptor. 136 func SaveState(fd uintptr) (*State, error) { 137 mode, e := winterm.GetConsoleMode(fd) 138 if e != nil { 139 return nil, e 140 } 141 return &State{mode}, nil 142 } 143 144 // DisableEcho disables echo for the terminal connected to the given file descriptor. 145 // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 146 func DisableEcho(fd uintptr, state *State) error { 147 mode := state.mode 148 mode &^= winterm.ENABLE_ECHO_INPUT 149 mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT 150 151 err := winterm.SetConsoleMode(fd, mode) 152 if err != nil { 153 return err 154 } 155 156 // Register an interrupt handler to catch and restore prior state 157 restoreAtInterrupt(fd, state) 158 return nil 159 } 160 161 // SetRawTerminal puts the terminal connected to the given file descriptor into raw 162 // mode and returns the previous state. 163 func SetRawTerminal(fd uintptr) (*State, error) { 164 state, err := MakeRaw(fd) 165 if err != nil { 166 return nil, err 167 } 168 169 // Register an interrupt handler to catch and restore prior state 170 restoreAtInterrupt(fd, state) 171 return state, err 172 } 173 174 // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw 175 // mode and returns the previous state of the terminal so that it can be restored. 176 func MakeRaw(fd uintptr) (*State, error) { 177 state, err := SaveState(fd) 178 if err != nil { 179 return nil, err 180 } 181 182 // See 183 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx 184 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 185 mode := state.mode 186 187 // Disable these modes 188 mode &^= winterm.ENABLE_ECHO_INPUT 189 mode &^= winterm.ENABLE_LINE_INPUT 190 mode &^= winterm.ENABLE_MOUSE_INPUT 191 mode &^= winterm.ENABLE_WINDOW_INPUT 192 mode &^= winterm.ENABLE_PROCESSED_INPUT 193 194 // Enable these modes 195 mode |= winterm.ENABLE_EXTENDED_FLAGS 196 mode |= winterm.ENABLE_INSERT_MODE 197 mode |= winterm.ENABLE_QUICK_EDIT_MODE 198 199 err = winterm.SetConsoleMode(fd, mode) 200 if err != nil { 201 return nil, err 202 } 203 return state, nil 204 } 205 206 func restoreAtInterrupt(fd uintptr, state *State) { 207 sigchan := make(chan os.Signal, 1) 208 signal.Notify(sigchan, os.Interrupt) 209 210 go func() { 211 _ = <-sigchan 212 RestoreTerminal(fd, state) 213 os.Exit(0) 214 }() 215 }