term_win.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. // +build windows
  2. package termutil
  3. import (
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "strconv"
  8. "syscall"
  9. "unsafe"
  10. )
  11. var (
  12. tty = os.Stdin
  13. unlockSignals = []os.Signal{
  14. os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL,
  15. }
  16. )
  17. var (
  18. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  19. // GetConsoleScreenBufferInfo retrieves information about the
  20. // specified console screen buffer.
  21. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
  22. procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
  23. // GetConsoleMode retrieves the current input mode of a console's
  24. // input buffer or the current output mode of a console screen buffer.
  25. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
  26. getConsoleMode = kernel32.NewProc("GetConsoleMode")
  27. // SetConsoleMode sets the input mode of a console's input buffer
  28. // or the output mode of a console screen buffer.
  29. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
  30. setConsoleMode = kernel32.NewProc("SetConsoleMode")
  31. // SetConsoleCursorPosition sets the cursor position in the
  32. // specified console screen buffer.
  33. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx
  34. setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
  35. mingw = isMingw()
  36. )
  37. type (
  38. // Defines the coordinates of the upper left and lower right corners
  39. // of a rectangle.
  40. // See
  41. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx
  42. smallRect struct {
  43. Left, Top, Right, Bottom int16
  44. }
  45. // Defines the coordinates of a character cell in a console screen
  46. // buffer. The origin of the coordinate system (0,0) is at the top, left cell
  47. // of the buffer.
  48. // See
  49. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx
  50. coordinates struct {
  51. X, Y int16
  52. }
  53. word int16
  54. // Contains information about a console screen buffer.
  55. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
  56. consoleScreenBufferInfo struct {
  57. dwSize coordinates
  58. dwCursorPosition coordinates
  59. wAttributes word
  60. srWindow smallRect
  61. dwMaximumWindowSize coordinates
  62. }
  63. )
  64. // TerminalWidth returns width of the terminal.
  65. func TerminalWidth() (width int, err error) {
  66. if mingw {
  67. return termWidthTPut()
  68. }
  69. return termWidthCmd()
  70. }
  71. func termWidthCmd() (width int, err error) {
  72. var info consoleScreenBufferInfo
  73. _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
  74. if e != 0 {
  75. return 0, error(e)
  76. }
  77. return int(info.dwSize.X) - 1, nil
  78. }
  79. func isMingw() bool {
  80. return os.Getenv("MINGW_PREFIX") != "" || os.Getenv("MSYSTEM") == "MINGW64"
  81. }
  82. func termWidthTPut() (width int, err error) {
  83. // TODO: maybe anybody knows a better way to get it on mintty...
  84. var res []byte
  85. cmd := exec.Command("tput", "cols")
  86. cmd.Stdin = os.Stdin
  87. if res, err = cmd.CombinedOutput(); err != nil {
  88. return 0, fmt.Errorf("%s: %v", string(res), err)
  89. }
  90. if len(res) > 1 {
  91. res = res[:len(res)-1]
  92. }
  93. return strconv.Atoi(string(res))
  94. }
  95. func getCursorPos() (pos coordinates, err error) {
  96. var info consoleScreenBufferInfo
  97. _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0)
  98. if e != 0 {
  99. return info.dwCursorPosition, error(e)
  100. }
  101. return info.dwCursorPosition, nil
  102. }
  103. func setCursorPos(pos coordinates) error {
  104. _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0)
  105. if e != 0 {
  106. return error(e)
  107. }
  108. return nil
  109. }
  110. var oldState word
  111. func lockEcho() (err error) {
  112. if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 {
  113. err = fmt.Errorf("Can't get terminal settings: %v", e)
  114. return
  115. }
  116. newState := oldState
  117. const ENABLE_ECHO_INPUT = 0x0004
  118. const ENABLE_LINE_INPUT = 0x0002
  119. newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))
  120. if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 {
  121. err = fmt.Errorf("Can't set terminal settings: %v", e)
  122. return
  123. }
  124. return
  125. }
  126. func unlockEcho() (err error) {
  127. if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 {
  128. err = fmt.Errorf("Can't set terminal settings")
  129. }
  130. return
  131. }