terminal_windows.go 1.9 KB

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