forked from adamlaska/moby
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathterm_windows.go
More file actions
305 lines (256 loc) · 8.11 KB
/
term_windows.go
File metadata and controls
305 lines (256 loc) · 8.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// +build windows
package term
import (
"io"
"os"
"os/signal"
"syscall"
"github.com/Azure/go-ansiterm/winterm"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term/windows"
)
// State holds the console mode for the terminal.
type State struct {
inMode, outMode uint32
inHandle, outHandle syscall.Handle
}
// Winsize is used for window size.
type Winsize struct {
Height uint16
Width uint16
x uint16
y uint16
}
const (
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
enableVirtualTerminalInput = 0x0200
enableVirtualTerminalProcessing = 0x0004
)
// usingNativeConsole is true if we are using the Windows native console
var usingNativeConsole bool
// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
switch {
case os.Getenv("ConEmuANSI") == "ON":
// The ConEmu terminal emulates ANSI on output streams well.
return windows.ConEmuStreams()
case os.Getenv("MSYSTEM") != "":
// MSYS (mingw) does not emulate ANSI well.
return windows.ConsoleStreams()
default:
if useNativeConsole() {
usingNativeConsole = true
return os.Stdin, os.Stdout, os.Stderr
}
return windows.ConsoleStreams()
}
}
// useNativeConsole determines if the docker client should use the built-in
// console which supports ANSI emulation, or fall-back to the golang emulator
// (github.com/azure/go-ansiterm).
func useNativeConsole() bool {
osv, err := system.GetOSVersion()
if err != nil {
return false
}
// Native console is not available before major version 10
if osv.MajorVersion < 10 {
return false
}
// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
if osv.Build < 10578 {
return false
}
// Get the console modes. If this fails, we can't use the native console
state, err := getNativeConsole()
if err != nil {
return false
}
// Probe the console to see if it can be enabled.
if nil != probeNativeConsole(state) {
return false
}
// Environment variable override
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
if e == "1" {
return true
}
return false
}
// TODO Windows. The native emulator still has issues which
// mean it shouldn't be enabled for everyone. Change this next line to true
// to change the default to "enable if available". In the meantime, users
// can still try it out by using USE_NATIVE_CONSOLE env variable.
return false
}
// getNativeConsole returns the console modes ('state') for the native Windows console
func getNativeConsole() (State, error) {
var (
err error
state State
)
// Get the handle to stdout
if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
return state, err
}
// Get the console mode from the consoles stdout handle
if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
return state, err
}
// Get the handle to stdin
if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
return state, err
}
// Get the console mode from the consoles stdin handle
if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
return state, err
}
return state, nil
}
// probeNativeConsole probes the console to determine if native can be supported,
func probeNativeConsole(state State) error {
if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
return err
}
defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
return err
}
defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
return nil
}
// enableNativeConsole turns on native console mode
func enableNativeConsole(state State) error {
if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
return err
}
if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can
return err
}
return nil
}
// disableNativeConsole turns off native console mode
func disableNativeConsole(state *State) error {
// Try and restore both in an out before error checking.
errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
if errout != nil {
return errout
}
if errin != nil {
return errin
}
return nil
}
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
return windows.GetHandleInfo(in)
}
// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
info, err := winterm.GetConsoleScreenBufferInfo(fd)
if err != nil {
return nil, err
}
winsize := &Winsize{
Width: uint16(info.Window.Right - info.Window.Left + 1),
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
x: 0,
y: 0}
return winsize, nil
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
return windows.IsConsole(fd)
}
// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
if usingNativeConsole {
return disableNativeConsole(state)
}
return winterm.SetConsoleMode(fd, state.outMode)
}
// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
if usingNativeConsole {
state, err := getNativeConsole()
if err != nil {
return nil, err
}
return &state, nil
}
mode, e := winterm.GetConsoleMode(fd)
if e != nil {
return nil, e
}
return &State{outMode: mode}, nil
}
// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
mode := state.inMode
mode &^= winterm.ENABLE_ECHO_INPUT
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
err := winterm.SetConsoleMode(fd, mode)
if err != nil {
return err
}
// Register an interrupt handler to catch and restore prior state
restoreAtInterrupt(fd, state)
return nil
}
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
func SetRawTerminal(fd uintptr) (*State, error) {
state, err := MakeRaw(fd)
if err != nil {
return nil, err
}
// Register an interrupt handler to catch and restore prior state
restoreAtInterrupt(fd, state)
return state, err
}
// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
state, err := SaveState(fd)
if err != nil {
return nil, err
}
mode := state.inMode
if usingNativeConsole {
if err := enableNativeConsole(*state); err != nil {
return nil, err
}
mode |= enableVirtualTerminalInput
}
// See
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
// Disable these modes
mode &^= winterm.ENABLE_ECHO_INPUT
mode &^= winterm.ENABLE_LINE_INPUT
mode &^= winterm.ENABLE_MOUSE_INPUT
mode &^= winterm.ENABLE_WINDOW_INPUT
mode &^= winterm.ENABLE_PROCESSED_INPUT
// Enable these modes
mode |= winterm.ENABLE_EXTENDED_FLAGS
mode |= winterm.ENABLE_INSERT_MODE
mode |= winterm.ENABLE_QUICK_EDIT_MODE
err = winterm.SetConsoleMode(fd, mode)
if err != nil {
return nil, err
}
return state, nil
}
func restoreAtInterrupt(fd uintptr, state *State) {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, os.Interrupt)
go func() {
_ = <-sigchan
RestoreTerminal(fd, state)
os.Exit(0)
}()
}