Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -15,7 +15,8 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
// State holds the console mode for the terminal. |
| 17 | 17 |
type State struct {
|
| 18 |
- mode uint32 |
|
| 18 |
+ inMode, outMode uint32 |
|
| 19 |
+ inHandle, outHandle syscall.Handle |
|
| 19 | 20 |
} |
| 20 | 21 |
|
| 21 | 22 |
// Winsize is used for window size. |
| ... | ... |
@@ -26,6 +27,15 @@ type Winsize struct {
|
| 26 | 26 |
y uint16 |
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
+const ( |
|
| 30 |
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx |
|
| 31 |
+ enableVirtualTerminalInput = 0x0200 |
|
| 32 |
+ enableVirtualTerminalProcessing = 0x0004 |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+// usingNativeConsole is true if we are using the Windows native console |
|
| 36 |
+var usingNativeConsole bool |
|
| 37 |
+ |
|
| 29 | 38 |
// StdStreams returns the standard streams (stdin, stdout, stedrr). |
| 30 | 39 |
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
| 31 | 40 |
switch {
|
| ... | ... |
@@ -37,6 +47,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
| 37 | 37 |
return windows.ConsoleStreams() |
| 38 | 38 |
default: |
| 39 | 39 |
if useNativeConsole() {
|
| 40 |
+ usingNativeConsole = true |
|
| 40 | 41 |
return os.Stdin, os.Stdout, os.Stderr |
| 41 | 42 |
} |
| 42 | 43 |
return windows.ConsoleStreams() |
| ... | ... |
@@ -52,7 +63,7 @@ func useNativeConsole() bool {
|
| 52 | 52 |
return false |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
- // Native console is not available major version 10 |
|
| 55 |
+ // Native console is not available before major version 10 |
|
| 56 | 56 |
if osv.MajorVersion < 10 {
|
| 57 | 57 |
return false |
| 58 | 58 |
} |
| ... | ... |
@@ -62,6 +73,17 @@ func useNativeConsole() bool {
|
| 62 | 62 |
return false |
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 |
+ // Get the console modes. If this fails, we can't use the native console |
|
| 66 |
+ state, err := getNativeConsole() |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ return false |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ // Probe the console to see if it can be enabled. |
|
| 72 |
+ if nil != probeNativeConsole(state) {
|
|
| 73 |
+ return false |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 65 | 76 |
// Environment variable override |
| 66 | 77 |
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
|
| 67 | 78 |
if e == "1" {
|
| ... | ... |
@@ -70,30 +92,84 @@ func useNativeConsole() bool {
|
| 70 | 70 |
return false |
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 |
+ // TODO Windows. The native emulator still has issues which |
|
| 74 |
+ // mean it shouldn't be enabled for everyone. Change this next line to true |
|
| 75 |
+ // to change the default to "enable if available". In the meantime, users |
|
| 76 |
+ // can still try it out by using USE_NATIVE_CONSOLE env variable. |
|
| 77 |
+ return false |
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+// getNativeConsole returns the console modes ('state') for the native Windows console
|
|
| 81 |
+func getNativeConsole() (State, error) {
|
|
| 82 |
+ var ( |
|
| 83 |
+ err error |
|
| 84 |
+ state State |
|
| 85 |
+ ) |
|
| 86 |
+ |
|
| 73 | 87 |
// Get the handle to stdout |
| 74 |
- stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE) |
|
| 75 |
- if err != nil {
|
|
| 76 |
- return false |
|
| 88 |
+ if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
|
|
| 89 |
+ return state, err |
|
| 77 | 90 |
} |
| 78 | 91 |
|
| 79 | 92 |
// 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 |
|
| 93 |
+ if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
|
|
| 94 |
+ return state, err |
|
| 83 | 95 |
} |
| 84 | 96 |
|
| 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 |
|
| 97 |
+ // Get the handle to stdin |
|
| 98 |
+ if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
|
|
| 99 |
+ return state, err |
|
| 90 | 100 |
} |
| 91 | 101 |
|
| 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 |
|
| 102 |
+ // Get the console mode from the consoles stdin handle |
|
| 103 |
+ if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
|
|
| 104 |
+ return state, err |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ return state, nil |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+// probeNativeConsole probes the console to determine if native can be supported, |
|
| 111 |
+func probeNativeConsole(state State) error {
|
|
| 112 |
+ if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
|
|
| 113 |
+ return err |
|
| 114 |
+ } |
|
| 115 |
+ defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) |
|
| 116 |
+ |
|
| 117 |
+ if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
|
|
| 118 |
+ return err |
|
| 119 |
+ } |
|
| 120 |
+ defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode) |
|
| 121 |
+ |
|
| 122 |
+ return nil |
|
| 123 |
+} |
|
| 124 |
+ |
|
| 125 |
+// enableNativeConsole turns on native console mode |
|
| 126 |
+func enableNativeConsole(state State) error {
|
|
| 127 |
+ if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
|
|
| 128 |
+ return err |
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
|
|
| 132 |
+ winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can |
|
| 133 |
+ return err |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ return nil |
|
| 137 |
+} |
|
| 138 |
+ |
|
| 139 |
+// disableNativeConsole turns off native console mode |
|
| 140 |
+func disableNativeConsole(state *State) error {
|
|
| 141 |
+ // Try and restore both in an out before error checking. |
|
| 142 |
+ errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) |
|
| 143 |
+ errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode) |
|
| 144 |
+ if errout != nil {
|
|
| 145 |
+ return errout |
|
| 146 |
+ } |
|
| 147 |
+ if errin != nil {
|
|
| 148 |
+ return errin |
|
| 149 |
+ } |
|
| 150 |
+ return nil |
|
| 97 | 151 |
} |
| 98 | 152 |
|
| 99 | 153 |
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. |
| ... | ... |
@@ -103,7 +179,6 @@ func GetFdInfo(in interface{}) (uintptr, bool) {
|
| 103 | 103 |
|
| 104 | 104 |
// GetWinsize returns the window size based on the specified file descriptor. |
| 105 | 105 |
func GetWinsize(fd uintptr) (*Winsize, error) {
|
| 106 |
- |
|
| 107 | 106 |
info, err := winterm.GetConsoleScreenBufferInfo(fd) |
| 108 | 107 |
if err != nil {
|
| 109 | 108 |
return nil, err |
| ... | ... |
@@ -115,9 +190,6 @@ func GetWinsize(fd uintptr) (*Winsize, error) {
|
| 115 | 115 |
x: 0, |
| 116 | 116 |
y: 0} |
| 117 | 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 | 118 |
return winsize, nil |
| 122 | 119 |
} |
| 123 | 120 |
|
| ... | ... |
@@ -129,25 +201,36 @@ func IsTerminal(fd uintptr) bool {
|
| 129 | 129 |
// RestoreTerminal restores the terminal connected to the given file descriptor |
| 130 | 130 |
// to a previous state. |
| 131 | 131 |
func RestoreTerminal(fd uintptr, state *State) error {
|
| 132 |
- return winterm.SetConsoleMode(fd, state.mode) |
|
| 132 |
+ if usingNativeConsole {
|
|
| 133 |
+ return disableNativeConsole(state) |
|
| 134 |
+ } |
|
| 135 |
+ return winterm.SetConsoleMode(fd, state.outMode) |
|
| 133 | 136 |
} |
| 134 | 137 |
|
| 135 | 138 |
// SaveState saves the state of the terminal connected to the given file descriptor. |
| 136 | 139 |
func SaveState(fd uintptr) (*State, error) {
|
| 140 |
+ if usingNativeConsole {
|
|
| 141 |
+ state, err := getNativeConsole() |
|
| 142 |
+ if err != nil {
|
|
| 143 |
+ return nil, err |
|
| 144 |
+ } |
|
| 145 |
+ return &state, nil |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 137 | 148 |
mode, e := winterm.GetConsoleMode(fd) |
| 138 | 149 |
if e != nil {
|
| 139 | 150 |
return nil, e |
| 140 | 151 |
} |
| 141 |
- return &State{mode}, nil
|
|
| 152 |
+ |
|
| 153 |
+ return &State{outMode: mode}, nil
|
|
| 142 | 154 |
} |
| 143 | 155 |
|
| 144 | 156 |
// DisableEcho disables echo for the terminal connected to the given file descriptor. |
| 145 | 157 |
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
| 146 | 158 |
func DisableEcho(fd uintptr, state *State) error {
|
| 147 |
- mode := state.mode |
|
| 159 |
+ mode := state.inMode |
|
| 148 | 160 |
mode &^= winterm.ENABLE_ECHO_INPUT |
| 149 | 161 |
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT |
| 150 |
- |
|
| 151 | 162 |
err := winterm.SetConsoleMode(fd, mode) |
| 152 | 163 |
if err != nil {
|
| 153 | 164 |
return err |
| ... | ... |
@@ -179,10 +262,17 @@ func MakeRaw(fd uintptr) (*State, error) {
|
| 179 | 179 |
return nil, err |
| 180 | 180 |
} |
| 181 | 181 |
|
| 182 |
+ mode := state.inMode |
|
| 183 |
+ if usingNativeConsole {
|
|
| 184 |
+ if err := enableNativeConsole(*state); err != nil {
|
|
| 185 |
+ return nil, err |
|
| 186 |
+ } |
|
| 187 |
+ mode |= enableVirtualTerminalInput |
|
| 188 |
+ } |
|
| 189 |
+ |
|
| 182 | 190 |
// See |
| 183 | 191 |
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx |
| 184 | 192 |
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx |
| 185 |
- mode := state.mode |
|
| 186 | 193 |
|
| 187 | 194 |
// Disable these modes |
| 188 | 195 |
mode &^= winterm.ENABLE_ECHO_INPUT |