|
@@ -15,7 +15,8 @@ import (
|
|
|
|
|
|
// State holds the console mode for the terminal.
|
|
// State holds the console mode for the terminal.
|
|
type State struct {
|
|
type State struct {
|
|
- mode uint32
|
|
|
|
|
|
+ inMode, outMode uint32
|
|
|
|
+ inHandle, outHandle syscall.Handle
|
|
}
|
|
}
|
|
|
|
|
|
// Winsize is used for window size.
|
|
// Winsize is used for window size.
|
|
@@ -26,6 +27,15 @@ type Winsize struct {
|
|
y 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).
|
|
// StdStreams returns the standard streams (stdin, stdout, stedrr).
|
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
switch {
|
|
switch {
|
|
@@ -37,6 +47,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
|
return windows.ConsoleStreams()
|
|
return windows.ConsoleStreams()
|
|
default:
|
|
default:
|
|
if useNativeConsole() {
|
|
if useNativeConsole() {
|
|
|
|
+ usingNativeConsole = true
|
|
return os.Stdin, os.Stdout, os.Stderr
|
|
return os.Stdin, os.Stdout, os.Stderr
|
|
}
|
|
}
|
|
return windows.ConsoleStreams()
|
|
return windows.ConsoleStreams()
|
|
@@ -52,7 +63,7 @@ func useNativeConsole() bool {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
|
|
|
|
- // Native console is not available major version 10
|
|
|
|
|
|
+ // Native console is not available before major version 10
|
|
if osv.MajorVersion < 10 {
|
|
if osv.MajorVersion < 10 {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
@@ -62,6 +73,17 @@ func useNativeConsole() bool {
|
|
return false
|
|
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
|
|
// Environment variable override
|
|
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
|
|
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
|
|
if e == "1" {
|
|
if e == "1" {
|
|
@@ -70,30 +92,84 @@ func useNativeConsole() bool {
|
|
return false
|
|
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
|
|
// Get the handle to stdout
|
|
- stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
|
|
|
|
- if err != nil {
|
|
|
|
- return false
|
|
|
|
|
|
+ if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
|
|
|
|
+ return state, err
|
|
}
|
|
}
|
|
|
|
|
|
// Get the console mode from the consoles stdout handle
|
|
// Get the console mode from the consoles stdout handle
|
|
- var mode uint32
|
|
|
|
- if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
|
|
|
|
- return false
|
|
|
|
|
|
+ if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
|
|
|
|
+ return state, err
|
|
}
|
|
}
|
|
|
|
|
|
- // Legacy mode does not have native ANSI emulation.
|
|
|
|
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
|
|
|
- const enableVirtualTerminalProcessing = 0x0004
|
|
|
|
- if mode&enableVirtualTerminalProcessing == 0 {
|
|
|
|
- return false
|
|
|
|
|
|
+ // Get the handle to stdin
|
|
|
|
+ if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
|
|
|
|
+ return state, err
|
|
}
|
|
}
|
|
|
|
|
|
- // TODO Windows (Post TP4). 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
|
|
|
|
|
|
+ // 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.
|
|
// 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) {
|
|
|
|
|
|
// GetWinsize returns the window size based on the specified file descriptor.
|
|
// GetWinsize returns the window size based on the specified file descriptor.
|
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
-
|
|
|
|
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
|
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -115,9 +190,6 @@ func GetWinsize(fd uintptr) (*Winsize, error) {
|
|
x: 0,
|
|
x: 0,
|
|
y: 0}
|
|
y: 0}
|
|
|
|
|
|
- // Note: GetWinsize is called frequently -- uncomment only for excessive details
|
|
|
|
- // logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
|
|
|
|
- // logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
|
|
|
|
return winsize, nil
|
|
return winsize, nil
|
|
}
|
|
}
|
|
|
|
|
|
@@ -129,25 +201,36 @@ func IsTerminal(fd uintptr) bool {
|
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
|
// RestoreTerminal restores the terminal connected to the given file descriptor
|
|
// to a previous state.
|
|
// to a previous state.
|
|
func RestoreTerminal(fd uintptr, state *State) error {
|
|
func RestoreTerminal(fd uintptr, state *State) error {
|
|
- return winterm.SetConsoleMode(fd, state.mode)
|
|
|
|
|
|
+ if usingNativeConsole {
|
|
|
|
+ return disableNativeConsole(state)
|
|
|
|
+ }
|
|
|
|
+ return winterm.SetConsoleMode(fd, state.outMode)
|
|
}
|
|
}
|
|
|
|
|
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
|
func SaveState(fd uintptr) (*State, error) {
|
|
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)
|
|
mode, e := winterm.GetConsoleMode(fd)
|
|
if e != nil {
|
|
if e != nil {
|
|
return nil, e
|
|
return nil, e
|
|
}
|
|
}
|
|
- return &State{mode}, nil
|
|
|
|
|
|
+
|
|
|
|
+ return &State{outMode: mode}, nil
|
|
}
|
|
}
|
|
|
|
|
|
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
|
// 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
|
|
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
func DisableEcho(fd uintptr, state *State) error {
|
|
func DisableEcho(fd uintptr, state *State) error {
|
|
- mode := state.mode
|
|
|
|
|
|
+ mode := state.inMode
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
|
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
|
-
|
|
|
|
err := winterm.SetConsoleMode(fd, mode)
|
|
err := winterm.SetConsoleMode(fd, mode)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
@@ -179,10 +262,17 @@ func MakeRaw(fd uintptr) (*State, error) {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ mode := state.inMode
|
|
|
|
+ if usingNativeConsole {
|
|
|
|
+ if err := enableNativeConsole(*state); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ mode |= enableVirtualTerminalInput
|
|
|
|
+ }
|
|
|
|
+
|
|
// See
|
|
// See
|
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
|
// -- 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
|
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
|
- mode := state.mode
|
|
|
|
|
|
|
|
// Disable these modes
|
|
// Disable these modes
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|
|
mode &^= winterm.ENABLE_ECHO_INPUT
|