termenv_windows.go

  1//go:build windows
  2// +build windows
  3
  4package termenv
  5
  6import (
  7	"fmt"
  8	"os"
  9	"strconv"
 10
 11	"golang.org/x/sys/windows"
 12)
 13
 14func (o *Output) ColorProfile() Profile {
 15	if !o.isTTY() {
 16		return Ascii
 17	}
 18
 19	if o.environ.Getenv("ConEmuANSI") == "ON" {
 20		return TrueColor
 21	}
 22
 23	winVersion, _, buildNumber := windows.RtlGetNtVersionNumbers()
 24	if buildNumber < 10586 || winVersion < 10 {
 25		// No ANSI support before Windows 10 build 10586.
 26		if o.environ.Getenv("ANSICON") != "" {
 27			conVersion := o.environ.Getenv("ANSICON_VER")
 28			cv, err := strconv.ParseInt(conVersion, 10, 64)
 29			if err != nil || cv < 181 {
 30				// No 8 bit color support before v1.81 release.
 31				return ANSI
 32			}
 33
 34			return ANSI256
 35		}
 36
 37		return Ascii
 38	}
 39	if buildNumber < 14931 {
 40		// No true color support before build 14931.
 41		return ANSI256
 42	}
 43
 44	return TrueColor
 45}
 46
 47func (o Output) foregroundColor() Color {
 48	// default gray
 49	return ANSIColor(7)
 50}
 51
 52func (o Output) backgroundColor() Color {
 53	// default black
 54	return ANSIColor(0)
 55}
 56
 57// EnableWindowsANSIConsole enables virtual terminal processing on Windows
 58// platforms. This allows the use of ANSI escape sequences in Windows console
 59// applications. Ensure this gets called before anything gets rendered with
 60// termenv.
 61//
 62// Returns the original console mode and an error if one occurred.
 63func EnableWindowsANSIConsole() (uint32, error) {
 64	handle, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
 65	if err != nil {
 66		return 0, err
 67	}
 68
 69	var mode uint32
 70	err = windows.GetConsoleMode(handle, &mode)
 71	if err != nil {
 72		return 0, err
 73	}
 74
 75	// See https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
 76	if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
 77		vtpmode := mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
 78		if err := windows.SetConsoleMode(handle, vtpmode); err != nil {
 79			return 0, err
 80		}
 81	}
 82
 83	return mode, nil
 84}
 85
 86// RestoreWindowsConsole restores the console mode to a previous state.
 87func RestoreWindowsConsole(mode uint32) error {
 88	handle, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
 89	if err != nil {
 90		return err
 91	}
 92
 93	return windows.SetConsoleMode(handle, mode)
 94}
 95
 96// EnableVirtualTerminalProcessing enables virtual terminal processing on
 97// Windows for o and returns a function that restores o to its previous state.
 98// On non-Windows platforms, or if o does not refer to a terminal, then it
 99// returns a non-nil no-op function and no error.
100func EnableVirtualTerminalProcessing(o *Output) (restoreFunc func() error, err error) {
101	// There is nothing to restore until we set the console mode.
102	restoreFunc = func() error {
103		return nil
104	}
105
106	// If o is not a tty, then there is nothing to do.
107	tty, ok := o.Writer().(*os.File)
108	if tty == nil || !ok {
109		return
110	}
111
112	// Get the current console mode. If there is an error, assume that o is not
113	// a terminal, discard the error, and return.
114	var mode uint32
115	if err2 := windows.GetConsoleMode(windows.Handle(tty.Fd()), &mode); err2 != nil {
116		return
117	}
118
119	// If virtual terminal processing is already set, then there is nothing to
120	// do and nothing to restore.
121	if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING {
122		return
123	}
124
125	// Enable virtual terminal processing. See
126	// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
127	if err2 := windows.SetConsoleMode(windows.Handle(tty.Fd()), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err2 != nil {
128		err = fmt.Errorf("windows.SetConsoleMode: %w", err2)
129		return
130	}
131
132	// Set the restore function. We maintain a reference to the tty in the
133	// closure (rather than just its handle) to ensure that the tty is not
134	// closed by a finalizer.
135	restoreFunc = func() error {
136		return windows.SetConsoleMode(windows.Handle(tty.Fd()), mode)
137	}
138
139	return
140}