Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5d93a23

Browse files
Merge pull request go-kit#485 from ereOn/isterminal_msys2_support
Added msys2/cygwin terminal detection support
2 parents cdd47ca + a62210c commit 5d93a23

File tree

3 files changed

+148
-6
lines changed

3 files changed

+148
-6
lines changed

‎log/term/colorwriter_windows.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type colorWriter struct {
2525
// platform support for ANSI color codes. If w is not a terminal it is
2626
// returned unmodified.
2727
func NewColorWriter(w io.Writer) io.Writer {
28-
if !IsTerminal(w) {
28+
if !IsConsole(w) {
2929
return w
3030
}
3131

‎log/term/terminal_windows.go‎

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,95 @@
88
package term
99

1010
import (
11+
"encoding/binary"
1112
"io"
13+
"regexp"
1214
"syscall"
1315
"unsafe"
1416
)
1517

1618
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
1719

1820
var (
19-
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
21+
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
22+
msysPipeNameRegex = regexp.MustCompile(`\\(cygwin|msys)-\w+-pty\d?-(to|from)-master`)
23+
)
24+
25+
const (
26+
fileNameInfo = 0x02
2027
)
2128

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

‎log/term/terminal_windows_test.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package term
2+
3+
import (
4+
"fmt"
5+
"syscall"
6+
"testing"
7+
)
8+
9+
// +build windows
10+
11+
type myWriter struct {
12+
fd uintptr
13+
}
14+
15+
func (w *myWriter) Write(p []byte) (int, error) {
16+
return 0, fmt.Errorf("not implemented")
17+
}
18+
19+
func (w *myWriter) Fd() uintptr {
20+
return w.fd
21+
}
22+
23+
var procGetStdHandle = kernel32.NewProc("GetStdHandle")
24+
25+
const stdOutputHandle = ^uintptr(0) - 11 + 1
26+
27+
func getConsoleHandle() syscall.Handle {
28+
ptr, err := syscall.UTF16PtrFromString("CONOUT$")
29+
30+
if err != nil {
31+
panic(err)
32+
}
33+
34+
handle, err := syscall.CreateFile(ptr, syscall.GENERIC_READ|syscall.GENERIC_WRITE, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 0)
35+
36+
if err != nil {
37+
panic(err)
38+
}
39+
40+
return handle
41+
}
42+
43+
func TestIsTerminal(t *testing.T) {
44+
// This is necessary because depending on whether `go test` is called with
45+
// the `-v` option, stdout will or will not be bound, changing the behavior
46+
// of the test. So we refer to it directly to avoid flakyness.
47+
handle := getConsoleHandle()
48+
49+
writer := &myWriter{
50+
fd: uintptr(handle),
51+
}
52+
53+
if !IsTerminal(writer) {
54+
t.Errorf("output is supposed to be a terminal")
55+
}
56+
}
57+
58+
func TestIsConsole(t *testing.T) {
59+
// This is necessary because depending on whether `go test` is called with
60+
// the `-v` option, stdout will or will not be bound, changing the behavior
61+
// of the test. So we refer to it directly to avoid flakyness.
62+
handle := getConsoleHandle()
63+
64+
writer := &myWriter{
65+
fd: uintptr(handle),
66+
}
67+
68+
if !IsConsole(writer) {
69+
t.Errorf("output is supposed to be a console")
70+
}
71+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /