Skip to content

Commit

Permalink
Added msys2/cygwin terminal detection support
Browse files Browse the repository at this point in the history
  • Loading branch information
jkauffmann-ubi committed Mar 6, 2017
1 parent fadad6f commit 0ef6cea
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 3 deletions.
56 changes: 53 additions & 3 deletions log/term/terminal_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@
package term

import (
"bytes"
"encoding/binary"
"io"
"regexp"
"syscall"
"unsafe"

"golang.org/x/sys/windows"
"golang.org/x/text/encoding/unicode"
)

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

var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
FileNameInfo = 0x02
msysPipeNameRegex = regexp.MustCompile(`\\(cygwin|msys)-\w+-pty\d?-(to|from)-master`)
)

// IsTerminal returns true if w writes to a terminal.
Expand All @@ -25,7 +33,49 @@ func IsTerminal(w io.Writer) bool {
if !ok {
return false
}

handle := windows.Handle(fw.Fd())
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fw.Fd(), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
err := windows.GetConsoleMode(handle, &st)

if err == nil {
return true
}

// The terminal is not a cmd.exe terminal, let's try to detect MSYS2 terminals.

// MSYS2 terminal reports as a pipe.
if ft, err := windows.GetFileType(handle); err != nil || ft != windows.FILE_TYPE_PIPE {
return false
}

// MSYS2/Cygwin terminal's name looks like: \msys-dd50a72ab4668b33-pty2-to-master
data := make([]byte, 256, 256)

r, _, e := syscall.Syscall6(
procGetFileInformationByHandleEx.Addr(),
4,
fw.Fd(),
uintptr(FileNameInfo),
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
0,
0,
)

if r != 0 && e == 0 {
var size uint32
binary.Read(bytes.NewBuffer(data[:4]), binary.LittleEndian, &size)

utf16Decoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder()
name, err := utf16Decoder.Bytes(data[4 : size+4])

if err != nil {
return false
}

return msysPipeNameRegex.Match(name)
}

return false
}
37 changes: 37 additions & 0 deletions log/term/terminal_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package term

import (
"fmt"
"testing"

"golang.org/x/sys/windows"
)

// +build windows

type myWriter struct {
fd uintptr
}

func (w *myWriter) Write(p []byte) (int, error) {
return 0, fmt.Errorf("not implemented")
}

func (w *myWriter) Fd() uintptr {
return w.fd
}

func TestIsTerminal(t *testing.T) {
handle, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)

if err != nil {
panic(err)
}

writer := &myWriter{
fd: uintptr(handle),
}
if !IsTerminal(writer) {
t.Errorf("output is supposed to be a terminal")
}
}

0 comments on commit 0ef6cea

Please sign in to comment.