244 lines
6.3 KiB
Go
244 lines
6.3 KiB
Go
// +build windows
|
|
|
|
// Display color on windows
|
|
// refer:
|
|
// golang.org/x/sys/windows
|
|
// golang.org/x/crypto/ssh/terminal
|
|
// https://docs.microsoft.com/en-us/windows/console
|
|
package color
|
|
|
|
import (
|
|
"os"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/xo/terminfo"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// related docs
|
|
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
|
|
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
|
var (
|
|
// isMSys bool
|
|
kernel32 *syscall.LazyDLL
|
|
|
|
procGetConsoleMode *syscall.LazyProc
|
|
procSetConsoleMode *syscall.LazyProc
|
|
)
|
|
|
|
func init() {
|
|
if !SupportColor() {
|
|
isLikeInCmd = true
|
|
return
|
|
}
|
|
|
|
// if disabled.
|
|
if !Enable {
|
|
return
|
|
}
|
|
|
|
// if at windows's ConEmu, Cmder, putty ... terminals not need VTP
|
|
|
|
// -------- try force enable colors on windows terminal -------
|
|
tryEnableVTP(needVTP)
|
|
|
|
// fetch console screen buffer info
|
|
// err := getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo)
|
|
}
|
|
|
|
// try force enable colors on windows terminal
|
|
func tryEnableVTP(enable bool) bool {
|
|
if !enable {
|
|
return false
|
|
}
|
|
|
|
debugf("True-Color by enable VirtualTerminalProcessing on windows")
|
|
|
|
initKernel32Proc()
|
|
|
|
// enable colors on windows terminal
|
|
if tryEnableOnCONOUT() {
|
|
return true
|
|
}
|
|
|
|
return tryEnableOnStdout()
|
|
}
|
|
|
|
func initKernel32Proc() {
|
|
if kernel32 != nil {
|
|
return
|
|
}
|
|
|
|
// load related windows dll
|
|
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
|
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
|
}
|
|
|
|
func tryEnableOnCONOUT() bool {
|
|
outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
|
if err != nil {
|
|
saveInternalError(err)
|
|
return false
|
|
}
|
|
|
|
err = EnableVirtualTerminalProcessing(outHandle, true)
|
|
if err != nil {
|
|
saveInternalError(err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func tryEnableOnStdout() bool {
|
|
// try direct open syscall.Stdout
|
|
err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
|
if err != nil {
|
|
saveInternalError(err)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Get the Windows Version and Build Number
|
|
var (
|
|
winVersion, _, buildNumber = windows.RtlGetNtVersionNumbers()
|
|
)
|
|
|
|
// refer
|
|
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57
|
|
// https://github.com/gookit/color/issues/25#issuecomment-738727917
|
|
// detects the Color Level Supported on windows: cmd, powerShell
|
|
func detectSpecialTermColor(termVal string) (tl terminfo.ColorLevel, needVTP bool) {
|
|
if os.Getenv("ConEmuANSI") == "ON" {
|
|
debugf("support True Color by ConEmuANSI=ON")
|
|
// ConEmuANSI is "ON" for generic ANSI support
|
|
// but True Color option is enabled by default
|
|
// I am just assuming that people wouldn't have disabled it
|
|
// Even if it is not enabled then ConEmu will auto round off
|
|
// accordingly
|
|
return terminfo.ColorLevelMillions, false
|
|
}
|
|
|
|
// Before Windows 10 Build Number 10586, console never supported ANSI Colors
|
|
if buildNumber < 10586 || winVersion < 10 {
|
|
// Detect if using ANSICON on older systems
|
|
if os.Getenv("ANSICON") != "" {
|
|
conVersion := os.Getenv("ANSICON_VER")
|
|
// 8 bit Colors were only supported after v1.81 release
|
|
if conVersion >= "181" {
|
|
return terminfo.ColorLevelHundreds, false
|
|
}
|
|
return terminfo.ColorLevelBasic, false
|
|
}
|
|
|
|
return terminfo.ColorLevelNone, false
|
|
}
|
|
|
|
// True Color is not available before build 14931 so fallback to 8 bit color.
|
|
if buildNumber < 14931 {
|
|
return terminfo.ColorLevelHundreds, true
|
|
}
|
|
|
|
// Windows 10 build 14931 is the first release that supports 16m/TrueColor
|
|
debugf("support True Color on windows version is >= build 14931")
|
|
return terminfo.ColorLevelMillions, true
|
|
}
|
|
|
|
/*************************************************************
|
|
* render full color code on windows(8,16,24bit color)
|
|
*************************************************************/
|
|
|
|
// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters
|
|
const (
|
|
// equals to docs page's ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
|
|
EnableVirtualTerminalProcessingMode uint32 = 0x4
|
|
)
|
|
|
|
// EnableVirtualTerminalProcessing Enable virtual terminal processing
|
|
//
|
|
// ref from github.com/konsorten/go-windows-terminal-sequences
|
|
// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
|
//
|
|
// Usage:
|
|
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
|
// // support print color text
|
|
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
|
|
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
|
|
var mode uint32
|
|
// Check if it is currently in the terminal
|
|
// err := syscall.GetConsoleMode(syscall.Stdout, &mode)
|
|
err := syscall.GetConsoleMode(stream, &mode)
|
|
if err != nil {
|
|
// fmt.Println("EnableVirtualTerminalProcessing", err)
|
|
return err
|
|
}
|
|
|
|
if enable {
|
|
mode |= EnableVirtualTerminalProcessingMode
|
|
} else {
|
|
mode &^= EnableVirtualTerminalProcessingMode
|
|
}
|
|
|
|
ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode))
|
|
if ret == 0 {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// renderColorCodeOnCmd enable cmd color render.
|
|
// func renderColorCodeOnCmd(fn func()) {
|
|
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
|
|
// // if is not in terminal, will clear color tag.
|
|
// if err != nil {
|
|
// // panic(err)
|
|
// fn()
|
|
// return
|
|
// }
|
|
//
|
|
// // force open color render
|
|
// old := ForceOpenColor()
|
|
// fn()
|
|
// // revert color setting
|
|
// supportColor = old
|
|
//
|
|
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// }
|
|
|
|
/*************************************************************
|
|
* render simple color code on windows
|
|
*************************************************************/
|
|
|
|
// IsTty returns true if the given file descriptor is a terminal.
|
|
func IsTty(fd uintptr) bool {
|
|
initKernel32Proc()
|
|
|
|
var st uint32
|
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
|
return r != 0 && e == 0
|
|
}
|
|
|
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
//
|
|
// Usage:
|
|
// fd := os.Stdout.Fd()
|
|
// fd := uintptr(syscall.Stdout) // for windows
|
|
// IsTerminal(fd)
|
|
func IsTerminal(fd uintptr) bool {
|
|
initKernel32Proc()
|
|
|
|
var st uint32
|
|
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
|
return r != 0 && e == 0
|
|
}
|