mfer/vendor/github.com/gookit/color/detect_windows.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
}