term_windows.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package term
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "os/signal"
  7. windowsconsole "github.com/moby/term/windows"
  8. "golang.org/x/sys/windows"
  9. )
  10. // terminalState holds the platform-specific state / console mode for the terminal.
  11. type terminalState struct {
  12. mode uint32
  13. }
  14. // vtInputSupported is true if winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported by the console
  15. var vtInputSupported bool
  16. func stdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
  17. // Turn on VT handling on all std handles, if possible. This might
  18. // fail, in which case we will fall back to terminal emulation.
  19. var (
  20. emulateStdin, emulateStdout, emulateStderr bool
  21. mode uint32
  22. )
  23. fd := windows.Handle(os.Stdin.Fd())
  24. if err := windows.GetConsoleMode(fd, &mode); err == nil {
  25. // Validate that winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
  26. if err = windows.SetConsoleMode(fd, mode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {
  27. emulateStdin = true
  28. } else {
  29. vtInputSupported = true
  30. }
  31. // Unconditionally set the console mode back even on failure because SetConsoleMode
  32. // remembers invalid bits on input handles.
  33. _ = windows.SetConsoleMode(fd, mode)
  34. }
  35. fd = windows.Handle(os.Stdout.Fd())
  36. if err := windows.GetConsoleMode(fd, &mode); err == nil {
  37. // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it.
  38. if err = windows.SetConsoleMode(fd, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
  39. emulateStdout = true
  40. } else {
  41. _ = windows.SetConsoleMode(fd, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
  42. }
  43. }
  44. fd = windows.Handle(os.Stderr.Fd())
  45. if err := windows.GetConsoleMode(fd, &mode); err == nil {
  46. // Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it.
  47. if err = windows.SetConsoleMode(fd, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING|windows.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
  48. emulateStderr = true
  49. } else {
  50. _ = windows.SetConsoleMode(fd, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
  51. }
  52. }
  53. if emulateStdin {
  54. h := uint32(windows.STD_INPUT_HANDLE)
  55. stdIn = windowsconsole.NewAnsiReader(int(h))
  56. } else {
  57. stdIn = os.Stdin
  58. }
  59. if emulateStdout {
  60. h := uint32(windows.STD_OUTPUT_HANDLE)
  61. stdOut = windowsconsole.NewAnsiWriter(int(h))
  62. } else {
  63. stdOut = os.Stdout
  64. }
  65. if emulateStderr {
  66. h := uint32(windows.STD_ERROR_HANDLE)
  67. stdErr = windowsconsole.NewAnsiWriter(int(h))
  68. } else {
  69. stdErr = os.Stderr
  70. }
  71. return stdIn, stdOut, stdErr
  72. }
  73. func getFdInfo(in interface{}) (uintptr, bool) {
  74. return windowsconsole.GetHandleInfo(in)
  75. }
  76. func getWinsize(fd uintptr) (*Winsize, error) {
  77. var info windows.ConsoleScreenBufferInfo
  78. if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
  79. return nil, err
  80. }
  81. winsize := &Winsize{
  82. Width: uint16(info.Window.Right - info.Window.Left + 1),
  83. Height: uint16(info.Window.Bottom - info.Window.Top + 1),
  84. }
  85. return winsize, nil
  86. }
  87. func setWinsize(fd uintptr, ws *Winsize) error {
  88. return fmt.Errorf("not implemented on Windows")
  89. }
  90. func isTerminal(fd uintptr) bool {
  91. var mode uint32
  92. err := windows.GetConsoleMode(windows.Handle(fd), &mode)
  93. return err == nil
  94. }
  95. func restoreTerminal(fd uintptr, state *State) error {
  96. return windows.SetConsoleMode(windows.Handle(fd), state.mode)
  97. }
  98. func saveState(fd uintptr) (*State, error) {
  99. var mode uint32
  100. if err := windows.GetConsoleMode(windows.Handle(fd), &mode); err != nil {
  101. return nil, err
  102. }
  103. return &State{mode: mode}, nil
  104. }
  105. func disableEcho(fd uintptr, state *State) error {
  106. // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
  107. mode := state.mode
  108. mode &^= windows.ENABLE_ECHO_INPUT
  109. mode |= windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT
  110. err := windows.SetConsoleMode(windows.Handle(fd), mode)
  111. if err != nil {
  112. return err
  113. }
  114. // Register an interrupt handler to catch and restore prior state
  115. restoreAtInterrupt(fd, state)
  116. return nil
  117. }
  118. func setRawTerminal(fd uintptr) (*State, error) {
  119. oldState, err := MakeRaw(fd)
  120. if err != nil {
  121. return nil, err
  122. }
  123. // Register an interrupt handler to catch and restore prior state
  124. restoreAtInterrupt(fd, oldState)
  125. return oldState, err
  126. }
  127. func setRawTerminalOutput(fd uintptr) (*State, error) {
  128. oldState, err := saveState(fd)
  129. if err != nil {
  130. return nil, err
  131. }
  132. // Ignore failures, since winterm.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this
  133. // version of Windows.
  134. _ = windows.SetConsoleMode(windows.Handle(fd), oldState.mode|windows.DISABLE_NEWLINE_AUTO_RETURN)
  135. return oldState, err
  136. }
  137. func restoreAtInterrupt(fd uintptr, state *State) {
  138. sigchan := make(chan os.Signal, 1)
  139. signal.Notify(sigchan, os.Interrupt)
  140. go func() {
  141. _ = <-sigchan
  142. _ = RestoreTerminal(fd, state)
  143. os.Exit(0)
  144. }()
  145. }