terminal_windows.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. //go:build windows && !appengine
  2. // +build windows,!appengine
  3. package terminal
  4. import (
  5. "bytes"
  6. "errors"
  7. "io"
  8. "os"
  9. "os/exec"
  10. "strconv"
  11. "strings"
  12. "syscall"
  13. "unsafe"
  14. )
  15. var kernel32 = syscall.NewLazyDLL("kernel32.dll")
  16. var (
  17. procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
  18. procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
  19. )
  20. const (
  21. enableProcessedOutput = 0x0001
  22. enableWrapAtEolOutput = 0x0002
  23. enableVirtualTerminalProcessing = 0x0004
  24. )
  25. func getVersion() (float64, int, error) {
  26. stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
  27. cmd := exec.Command("cmd", "ver")
  28. cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
  29. cmd.Stdout = stdout
  30. cmd.Stderr = stderr
  31. err := cmd.Run()
  32. if err != nil {
  33. return -1, -1, err
  34. }
  35. errCanNotDetermineVersion := errors.New("can't determine Windows version")
  36. lines := stdout.String()
  37. start := strings.IndexByte(lines, '[')
  38. end := strings.IndexByte(lines, ']')
  39. if start == -1 || end == -1 {
  40. return -1, -1, errCanNotDetermineVersion
  41. }
  42. winLine := lines[start+1 : end]
  43. if len(winLine) < 10 {
  44. return -1, -1, errCanNotDetermineVersion
  45. }
  46. // Version 10.0.15063
  47. versionsLine := winLine[strings.IndexByte(winLine, ' ')+1:]
  48. // 10.0.15063
  49. versionSems := strings.Split(versionsLine, ".")
  50. // 10
  51. // 0
  52. // 15063
  53. if len(versionSems) < 3 {
  54. return -1, -1, errCanNotDetermineVersion
  55. }
  56. buildNumber, _ := strconv.Atoi(versionSems[2])
  57. major, err := strconv.ParseFloat(versionSems[0], 64)
  58. return major, buildNumber, err
  59. }
  60. func init() { // modifies the "SupportColors" package-level variable.
  61. SupportColors = false
  62. major, buildNumber, err := getVersion()
  63. if err != nil {
  64. return
  65. }
  66. // Activate Virtual Processing for Windows CMD
  67. // Info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
  68. if major >= 10 {
  69. handle := syscall.Handle(os.Stderr.Fd())
  70. procSetConsoleMode.Call(uintptr(handle), enableProcessedOutput|enableWrapAtEolOutput|enableVirtualTerminalProcessing)
  71. // check specific for windows operating system
  72. // versions, after windows 10 microsoft
  73. // gave support for 256-color console.
  74. SupportColors = buildNumber >= 10586
  75. }
  76. }
  77. // IsTerminal returns true if stderr's file descriptor is a terminal.
  78. func IsTerminal(f io.Writer) bool {
  79. switch v := f.(type) {
  80. case *os.File:
  81. var st uint32
  82. r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
  83. return r != 0 && e == 0
  84. default:
  85. return false
  86. }
  87. }