1 | //go:build windows && !appengine
|
---|
2 | // +build windows,!appengine
|
---|
3 |
|
---|
4 | package isatty
|
---|
5 |
|
---|
6 | import (
|
---|
7 | "errors"
|
---|
8 | "strings"
|
---|
9 | "syscall"
|
---|
10 | "unicode/utf16"
|
---|
11 | "unsafe"
|
---|
12 | )
|
---|
13 |
|
---|
14 | const (
|
---|
15 | objectNameInfo uintptr = 1
|
---|
16 | fileNameInfo = 2
|
---|
17 | fileTypePipe = 3
|
---|
18 | )
|
---|
19 |
|
---|
20 | var (
|
---|
21 | kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
---|
22 | ntdll = syscall.NewLazyDLL("ntdll.dll")
|
---|
23 | procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
---|
24 | procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
---|
25 | procGetFileType = kernel32.NewProc("GetFileType")
|
---|
26 | procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
---|
27 | )
|
---|
28 |
|
---|
29 | func init() {
|
---|
30 | // Check if GetFileInformationByHandleEx is available.
|
---|
31 | if procGetFileInformationByHandleEx.Find() != nil {
|
---|
32 | procGetFileInformationByHandleEx = nil
|
---|
33 | }
|
---|
34 | }
|
---|
35 |
|
---|
36 | // IsTerminal return true if the file descriptor is terminal.
|
---|
37 | func IsTerminal(fd uintptr) bool {
|
---|
38 | var st uint32
|
---|
39 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
---|
40 | return r != 0 && e == 0
|
---|
41 | }
|
---|
42 |
|
---|
43 | // Check pipe name is used for cygwin/msys2 pty.
|
---|
44 | // Cygwin/MSYS2 PTY has a name like:
|
---|
45 | // \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
---|
46 | func isCygwinPipeName(name string) bool {
|
---|
47 | token := strings.Split(name, "-")
|
---|
48 | if len(token) < 5 {
|
---|
49 | return false
|
---|
50 | }
|
---|
51 |
|
---|
52 | if token[0] != `\msys` &&
|
---|
53 | token[0] != `\cygwin` &&
|
---|
54 | token[0] != `\Device\NamedPipe\msys` &&
|
---|
55 | token[0] != `\Device\NamedPipe\cygwin` {
|
---|
56 | return false
|
---|
57 | }
|
---|
58 |
|
---|
59 | if token[1] == "" {
|
---|
60 | return false
|
---|
61 | }
|
---|
62 |
|
---|
63 | if !strings.HasPrefix(token[2], "pty") {
|
---|
64 | return false
|
---|
65 | }
|
---|
66 |
|
---|
67 | if token[3] != `from` && token[3] != `to` {
|
---|
68 | return false
|
---|
69 | }
|
---|
70 |
|
---|
71 | if token[4] != "master" {
|
---|
72 | return false
|
---|
73 | }
|
---|
74 |
|
---|
75 | return true
|
---|
76 | }
|
---|
77 |
|
---|
78 | // getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
---|
79 | // since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion
|
---|
80 | // guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
---|
81 | // Windows vista to 10
|
---|
82 | // see https://stackoverflow.com/a/18792477 for details
|
---|
83 | func getFileNameByHandle(fd uintptr) (string, error) {
|
---|
84 | if procNtQueryObject == nil {
|
---|
85 | return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
---|
86 | }
|
---|
87 |
|
---|
88 | var buf [4 + syscall.MAX_PATH]uint16
|
---|
89 | var result int
|
---|
90 | r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
---|
91 | fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
---|
92 | if r != 0 {
|
---|
93 | return "", e
|
---|
94 | }
|
---|
95 | return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
---|
96 | }
|
---|
97 |
|
---|
98 | // IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
---|
99 | // terminal.
|
---|
100 | func IsCygwinTerminal(fd uintptr) bool {
|
---|
101 | if procGetFileInformationByHandleEx == nil {
|
---|
102 | name, err := getFileNameByHandle(fd)
|
---|
103 | if err != nil {
|
---|
104 | return false
|
---|
105 | }
|
---|
106 | return isCygwinPipeName(name)
|
---|
107 | }
|
---|
108 |
|
---|
109 | // Cygwin/msys's pty is a pipe.
|
---|
110 | ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
---|
111 | if ft != fileTypePipe || e != 0 {
|
---|
112 | return false
|
---|
113 | }
|
---|
114 |
|
---|
115 | var buf [2 + syscall.MAX_PATH]uint16
|
---|
116 | r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
---|
117 | 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
---|
118 | uintptr(len(buf)*2), 0, 0)
|
---|
119 | if r == 0 || e != 0 {
|
---|
120 | return false
|
---|
121 | }
|
---|
122 |
|
---|
123 | l := *(*uint32)(unsafe.Pointer(&buf))
|
---|
124 | return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
---|
125 | }
|
---|