7 | 7 |
package term
|
8 | 8 |
|
9 | 9 |
import (
|
|
10 |
"encoding/binary"
|
10 | 11 |
"io"
|
|
12 |
"regexp"
|
11 | 13 |
"syscall"
|
12 | 14 |
"unsafe"
|
13 | 15 |
)
|
|
15 | 17 |
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
16 | 18 |
|
17 | 19 |
var (
|
18 | |
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
20 |
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
|
21 |
msysPipeNameRegex = regexp.MustCompile(`\\(cygwin|msys)-\w+-pty\d?-(to|from)-master`)
|
|
22 |
)
|
|
23 |
|
|
24 |
const (
|
|
25 |
fileNameInfo = 0x02
|
19 | 26 |
)
|
20 | 27 |
|
21 | 28 |
// IsTerminal returns true if w writes to a terminal.
|
22 | 29 |
func IsTerminal(w io.Writer) bool {
|
23 | |
fw, ok := w.(fder)
|
24 | |
if !ok {
|
|
30 |
return IsConsole(w) || IsMSYSTerminal(w)
|
|
31 |
}
|
|
32 |
|
|
33 |
// IsConsole returns true if w writes to a Windows console.
|
|
34 |
func IsConsole(w io.Writer) bool {
|
|
35 |
var handle syscall.Handle
|
|
36 |
|
|
37 |
if fw, ok := w.(fder); ok {
|
|
38 |
handle = syscall.Handle(fw.Fd())
|
|
39 |
} else {
|
|
40 |
// The writer has no file-descriptor and so can't be a terminal.
|
25 | 41 |
return false
|
26 | 42 |
}
|
|
43 |
|
27 | 44 |
var st uint32
|
28 | |
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fw.Fd(), uintptr(unsafe.Pointer(&st)), 0)
|
29 | |
return r != 0 && e == 0
|
|
45 |
err := syscall.GetConsoleMode(handle, &st)
|
|
46 |
|
|
47 |
// If the handle is attached to a terminal, GetConsoleMode returns a
|
|
48 |
// non-zero value containing the console mode flags. We don't care about
|
|
49 |
// the specifics of flags, just that it is not zero.
|
|
50 |
return (err == nil && st != 0)
|
30 | 51 |
}
|
|
52 |
|
|
53 |
// IsMSYSTerminal returns true if w writes to a MSYS/MSYS2 terminal.
|
|
54 |
func IsMSYSTerminal(w io.Writer) bool {
|
|
55 |
var handle syscall.Handle
|
|
56 |
|
|
57 |
if fw, ok := w.(fder); ok {
|
|
58 |
handle = syscall.Handle(fw.Fd())
|
|
59 |
} else {
|
|
60 |
// The writer has no file-descriptor and so can't be a terminal.
|
|
61 |
return false
|
|
62 |
}
|
|
63 |
|
|
64 |
// MSYS(2) terminal reports as a pipe for STDIN/STDOUT/STDERR. If it isn't
|
|
65 |
// a pipe, it can't be a MSYS(2) terminal.
|
|
66 |
filetype, err := syscall.GetFileType(handle)
|
|
67 |
|
|
68 |
if filetype != syscall.FILE_TYPE_PIPE || err != nil {
|
|
69 |
return false
|
|
70 |
}
|
|
71 |
|
|
72 |
// MSYS2/Cygwin terminal's name looks like: \msys-dd50a72ab4668b33-pty2-to-master
|
|
73 |
data := make([]byte, 256, 256)
|
|
74 |
|
|
75 |
r, _, e := syscall.Syscall6(
|
|
76 |
procGetFileInformationByHandleEx.Addr(),
|
|
77 |
4,
|
|
78 |
uintptr(handle),
|
|
79 |
uintptr(fileNameInfo),
|
|
80 |
uintptr(unsafe.Pointer(&data[0])),
|
|
81 |
uintptr(len(data)),
|
|
82 |
0,
|
|
83 |
0,
|
|
84 |
)
|
|
85 |
|
|
86 |
if r != 0 && e == 0 {
|
|
87 |
// The first 4 bytes of the buffer are the size of the UTF16 name, in bytes.
|
|
88 |
unameLen := binary.LittleEndian.Uint32(data[:4]) / 2
|
|
89 |
uname := make([]uint16, unameLen, unameLen)
|
|
90 |
|
|
91 |
for i := uint32(0); i < unameLen; i++ {
|
|
92 |
uname[i] = binary.LittleEndian.Uint16(data[i*2+4 : i*2+2+4])
|
|
93 |
}
|
|
94 |
|
|
95 |
name := syscall.UTF16ToString(uname)
|
|
96 |
|
|
97 |
return msysPipeNameRegex.MatchString(name)
|
|
98 |
}
|
|
99 |
|
|
100 |
return false
|
|
101 |
}
|