term_windows.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // +build windows
  2. package term
  3. import (
  4. "io"
  5. "os"
  6. "os/signal"
  7. "github.com/Azure/go-ansiterm/winterm"
  8. "github.com/docker/docker/pkg/term/windows"
  9. "golang.org/x/sys/windows"
  10. )
  11. // State holds the console mode for the terminal.
  12. type State struct {
  13. mode uint32
  14. }
  15. // Winsize is used for window size.
  16. type Winsize struct {
  17. Height uint16
  18. Width uint16
  19. }
  20. const (
  21. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
  22. enableVirtualTerminalInput = 0x0200
  23. enableVirtualTerminalProcessing = 0x0004
  24. disableNewlineAutoReturn = 0x0008
  25. )
  26. // vtInputSupported is true if enableVirtualTerminalInput is supported by the console
  27. var vtInputSupported bool
  28. // StdStreams returns the standard streams (stdin, stdout, stderr).
  29. func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
  30. // Turn on VT handling on all std handles, if possible. This might
  31. // fail, in which case we will fall back to terminal emulation.
  32. var emulateStdin, emulateStdout, emulateStderr bool
  33. fd := os.Stdin.Fd()
  34. if mode, err := winterm.GetConsoleMode(fd); err == nil {
  35. // Validate that enableVirtualTerminalInput is supported, but do not set it.
  36. if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil {
  37. emulateStdin = true
  38. } else {
  39. vtInputSupported = true
  40. }
  41. // Unconditionally set the console mode back even on failure because SetConsoleMode
  42. // remembers invalid bits on input handles.
  43. winterm.SetConsoleMode(fd, mode)
  44. }
  45. fd = os.Stdout.Fd()
  46. if mode, err := winterm.GetConsoleMode(fd); err == nil {
  47. // Validate disableNewlineAutoReturn is supported, but do not set it.
  48. if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
  49. emulateStdout = true
  50. } else {
  51. winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
  52. }
  53. }
  54. fd = os.Stderr.Fd()
  55. if mode, err := winterm.GetConsoleMode(fd); err == nil {
  56. // Validate disableNewlineAutoReturn is supported, but do not set it.
  57. if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
  58. emulateStderr = true
  59. } else {
  60. winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
  61. }
  62. }
  63. if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" {
  64. // The ConEmu and ConsoleZ terminals emulate ANSI on output streams well.
  65. emulateStdin = true
  66. emulateStdout = false
  67. emulateStderr = false
  68. }
  69. if emulateStdin {
  70. stdIn = windowsconsole.NewAnsiReader(windows.STD_INPUT_HANDLE)
  71. } else {
  72. stdIn = os.Stdin
  73. }
  74. if emulateStdout {
  75. stdOut = windowsconsole.NewAnsiWriter(windows.STD_OUTPUT_HANDLE)
  76. } else {
  77. stdOut = os.Stdout
  78. }
  79. if emulateStderr {
  80. stdErr = windowsconsole.NewAnsiWriter(windows.STD_ERROR_HANDLE)
  81. } else {
  82. stdErr = os.Stderr
  83. }
  84. return
  85. }
  86. // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
  87. func GetFdInfo(in interface{}) (uintptr, bool) {
  88. return windowsconsole.GetHandleInfo(in)
  89. }
  90. // GetWinsize returns the window size based on the specified file descriptor.
  91. func GetWinsize(fd uintptr) (*Winsize, error) {
  92. info, err := winterm.GetConsoleScreenBufferInfo(fd)
  93. if err != nil {
  94. return nil, err
  95. }
  96. winsize := &Winsize{
  97. Width: uint16(info.Window.Right - info.Window.Left + 1),
  98. Height: uint16(info.Window.Bottom - info.Window.Top + 1),
  99. }
  100. return winsize, nil
  101. }
  102. // IsTerminal returns true if the given file descriptor is a terminal.
  103. func IsTerminal(fd uintptr) bool {
  104. return windowsconsole.IsConsole(fd)
  105. }
  106. // RestoreTerminal restores the terminal connected to the given file descriptor
  107. // to a previous state.
  108. func RestoreTerminal(fd uintptr, state *State) error {
  109. return winterm.SetConsoleMode(fd, state.mode)
  110. }
  111. // SaveState saves the state of the terminal connected to the given file descriptor.
  112. func SaveState(fd uintptr) (*State, error) {
  113. mode, e := winterm.GetConsoleMode(fd)
  114. if e != nil {
  115. return nil, e
  116. }
  117. return &State{mode: mode}, nil
  118. }
  119. // DisableEcho disables echo for the terminal connected to the given file descriptor.
  120. // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
  121. func DisableEcho(fd uintptr, state *State) error {
  122. mode := state.mode
  123. mode &^= winterm.ENABLE_ECHO_INPUT
  124. mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
  125. err := winterm.SetConsoleMode(fd, mode)
  126. if err != nil {
  127. return err
  128. }
  129. // Register an interrupt handler to catch and restore prior state
  130. restoreAtInterrupt(fd, state)
  131. return nil
  132. }
  133. // SetRawTerminal puts the terminal connected to the given file descriptor into
  134. // raw mode and returns the previous state. On UNIX, this puts both the input
  135. // and output into raw mode. On Windows, it only puts the input into raw mode.
  136. func SetRawTerminal(fd uintptr) (*State, error) {
  137. state, err := MakeRaw(fd)
  138. if err != nil {
  139. return nil, err
  140. }
  141. // Register an interrupt handler to catch and restore prior state
  142. restoreAtInterrupt(fd, state)
  143. return state, err
  144. }
  145. // SetRawTerminalOutput puts the output of terminal connected to the given file
  146. // descriptor into raw mode. On UNIX, this does nothing and returns nil for the
  147. // state. On Windows, it disables LF -> CRLF translation.
  148. func SetRawTerminalOutput(fd uintptr) (*State, error) {
  149. state, err := SaveState(fd)
  150. if err != nil {
  151. return nil, err
  152. }
  153. // Ignore failures, since disableNewlineAutoReturn might not be supported on this
  154. // version of Windows.
  155. winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
  156. return state, err
  157. }
  158. // MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
  159. // mode and returns the previous state of the terminal so that it can be restored.
  160. func MakeRaw(fd uintptr) (*State, error) {
  161. state, err := SaveState(fd)
  162. if err != nil {
  163. return nil, err
  164. }
  165. mode := state.mode
  166. // See
  167. // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
  168. // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
  169. // Disable these modes
  170. mode &^= winterm.ENABLE_ECHO_INPUT
  171. mode &^= winterm.ENABLE_LINE_INPUT
  172. mode &^= winterm.ENABLE_MOUSE_INPUT
  173. mode &^= winterm.ENABLE_WINDOW_INPUT
  174. mode &^= winterm.ENABLE_PROCESSED_INPUT
  175. // Enable these modes
  176. mode |= winterm.ENABLE_EXTENDED_FLAGS
  177. mode |= winterm.ENABLE_INSERT_MODE
  178. mode |= winterm.ENABLE_QUICK_EDIT_MODE
  179. if vtInputSupported {
  180. mode |= enableVirtualTerminalInput
  181. }
  182. err = winterm.SetConsoleMode(fd, mode)
  183. if err != nil {
  184. return nil, err
  185. }
  186. return state, nil
  187. }
  188. func restoreAtInterrupt(fd uintptr, state *State) {
  189. sigchan := make(chan os.Signal, 1)
  190. signal.Notify(sigchan, os.Interrupt)
  191. go func() {
  192. _ = <-sigchan
  193. RestoreTerminal(fd, state)
  194. os.Exit(0)
  195. }()
  196. }