1// +build windows
2
3// Display color on windows
4// refer:
5//  golang.org/x/sys/windows
6// 	golang.org/x/crypto/ssh/terminal
7// 	https://docs.microsoft.com/en-us/windows/console
8package color
9
10import (
11	"fmt"
12	"syscall"
13	"unsafe"
14)
15
16// color on windows cmd
17// you can see on windows by command: COLOR /?
18// windows color build by: "Bg + Fg" OR only "Fg"
19// Consists of any two of the following:
20// the first is the background color, and the second is the foreground color
21// 颜色属性由两个十六进制数字指定
22//  - 第一个对应于背景,第二个对应于前景。
23// 	- 当只传入一个值时,则认为是前景色
24// 每个数字可以为以下任何值:
25// more see: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
26const (
27	// Foreground colors.
28	winFgBlack  uint16 = 0x00 // 0 黑色
29	winFgBlue   uint16 = 0x01 // 1 蓝色
30	winFgGreen  uint16 = 0x02 // 2 绿色
31	winFgAqua   uint16 = 0x03 // 3 浅绿 skyblue
32	winFgRed    uint16 = 0x04 // 4 红色
33	winFgPink   uint16 = 0x05 // 5 紫色/品红
34	winFgYellow uint16 = 0x06 // 6 黄色
35	winFgWhite  uint16 = 0x07 // 7 白色
36	winFgGray   uint16 = 0x08 // 8 灰色
37
38	winFgLightBlue   uint16 = 0x09 // 9 淡蓝色
39	winFgLightGreen  uint16 = 0x0a // 10 淡绿色
40	winFgLightAqua   uint16 = 0x0b // 11 淡浅绿色
41	winFgLightRed    uint16 = 0x0c // 12 淡红色
42	winFgLightPink   uint16 = 0x0d // 13 Purple 淡紫色, Pink 粉红
43	winFgLightYellow uint16 = 0x0e // 14 淡黄色
44	winFgLightWhite  uint16 = 0x0f // 15 亮白色
45
46	// Background colors.
47	winBgBlack  uint16 = 0x00 // 黑色
48	winBgBlue   uint16 = 0x10 // 蓝色
49	winBgGreen  uint16 = 0x20 // 绿色
50	winBgAqua   uint16 = 0x30 // 浅绿 skyblue
51	winBgRed    uint16 = 0x40 // 红色
52	winBgPink   uint16 = 0x50 // 紫色
53	winBgYellow uint16 = 0x60 // 黄色
54	winBgWhite  uint16 = 0x70 // 白色
55	winBgGray   uint16 = 0x80 // 128 灰色
56
57	winBgLightBlue   uint16 = 0x90 // 淡蓝色
58	winBgLightGreen  uint16 = 0xa0 // 淡绿色
59	winBgLightAqua   uint16 = 0xb0 // 淡浅绿色
60	winBgLightRed    uint16 = 0xc0 // 淡红色
61	winBgLightPink   uint16 = 0xd0 // 淡紫色
62	winBgLightYellow uint16 = 0xe0 // 淡黄色
63	winBgLightWhite  uint16 = 0xf0 // 240 亮白色
64
65	// bg black, fg white
66	winDefSetting = winBgBlack | winFgWhite
67
68	// Option settings
69	// see https://docs.microsoft.com/en-us/windows/console/char-info-str
70	winFgIntensity uint16 = 0x0008 // 8 前景强度
71	winBgIntensity uint16 = 0x0080 // 128 背景强度
72
73	WinOpLeading    uint16 = 0x0100 // 前导字节
74	WinOpTrailing   uint16 = 0x0200 // 尾随字节
75	WinOpHorizontal uint16 = 0x0400 // 顶部水平
76	WinOpReverse    uint16 = 0x4000 // 反转前景和背景
77	WinOpUnderscore uint16 = 0x8000 // 32768 下划线
78)
79
80// color on windows
81var winColorsMap map[Color]uint16
82
83var (
84	// for cmd.exe
85	// echo %ESC%[1;33;40m Yellow on black %ESC%[0m
86	escChar = ""
87	// isMSys bool
88	kernel32 *syscall.LazyDLL
89
90	procGetConsoleMode *syscall.LazyProc
91	// procSetConsoleMode *syscall.LazyProc
92
93	procSetTextAttribute           *syscall.LazyProc
94	procGetConsoleScreenBufferInfo *syscall.LazyProc
95
96	// console screen buffer info
97	// eg {size:{x:215 y:3000} cursorPosition:{x:0 y:893} attributes:7 window:{left:0 top:882 right:214 bottom:893} maximumWindowSize:{x:215 y:170}}
98	defScreenInfo consoleScreenBufferInfo
99)
100
101func init() {
102	// if at linux, mac, or windows's ConEmu, Cmder, putty
103	if isSupportColor {
104		return
105	}
106
107	// init some info
108	isLikeInCmd = true
109	initWinColorsMap()
110
111	// isMSys = utils.IsMSys()
112	kernel32 = syscall.NewLazyDLL("kernel32.dll")
113
114	// https://docs.microsoft.com/en-us/windows/console/setconsolemode
115	procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
116	// procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
117
118	procSetTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
119	// https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo
120	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
121
122	// fetch console screen buffer info
123	getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo)
124}
125
126// initWinColorsMap init colors to win-colors mapping
127func initWinColorsMap() {
128	// init map
129	winColorsMap = map[Color]uint16{
130		// Foreground colors
131		FgBlack:   winFgBlack,
132		FgRed:     winFgRed,
133		FgGreen:   winFgGreen,
134		FgYellow:  winFgYellow,
135		FgBlue:    winFgBlue,
136		FgMagenta: winFgPink, // diff
137		FgCyan:    winFgAqua, // diff
138		FgWhite:   winFgWhite,
139		FgDefault: winFgWhite,
140
141		// Extra Foreground colors
142		FgDarkGray:     winFgGray,
143		FgLightRed:     winFgLightBlue,
144		FgLightGreen:   winFgLightGreen,
145		FgLightYellow:  winFgLightYellow,
146		FgLightBlue:    winFgLightRed,
147		FgLightMagenta: winFgLightPink,
148		FgLightCyan:    winFgLightAqua,
149		FgLightWhite:   winFgLightWhite,
150
151		// Background colors
152		BgBlack:   winBgBlack,
153		BgRed:     winBgRed,
154		BgGreen:   winBgGreen,
155		BgYellow:  winBgYellow,
156		BgBlue:    winBgBlue,
157		BgMagenta: winBgPink, // diff
158		BgCyan:    winBgAqua, // diff
159		BgWhite:   winBgWhite,
160		BgDefault: winBgBlack,
161
162		// Extra Background colors
163		BgDarkGray:     winBgGray,
164		BgLightRed:     winBgLightBlue,
165		BgLightGreen:   winBgLightGreen,
166		BgLightYellow:  winBgLightYellow,
167		BgLightBlue:    winBgLightRed,
168		BgLightMagenta: winBgLightPink,
169		BgLightCyan:    winBgLightAqua,
170		BgLightWhite:   winBgLightWhite,
171
172		// Option settings(注释掉的,将在win cmd中忽略掉)
173		// OpReset: winDefSetting,  // 重置所有设置
174		OpBold: winFgIntensity, // 加粗 ->
175		// OpFuzzy:                    // 模糊(不是所有的终端仿真器都支持)
176		// OpItalic                    // 斜体(不是所有的终端仿真器都支持)
177		OpUnderscore: WinOpUnderscore, // 下划线
178		// OpBlink                      // 闪烁
179		// OpFastBlink                  // 快速闪烁(未广泛支持)
180		// OpReverse: WinOpReverse      // 颠倒的 交换背景色与前景色
181		// OpConcealed                  // 隐匿的
182		// OpStrikethrough              // 删除的,删除线(未广泛支持)
183	}
184}
185
186// winPrint
187func winPrint(str string, colors ...Color) {
188	winInternalPrint(str, convertColorsToWinAttr(colors), false)
189}
190
191// winPrintln
192func winPrintln(str string, colors ...Color) {
193	winInternalPrint(str, convertColorsToWinAttr(colors), true)
194}
195
196// winInternalPrint
197// winInternalPrint("hello [OK];", 2|8, true) //亮绿色
198func winInternalPrint(str string, attribute uint16, newline bool) (int, error) {
199	if !Enable { // not enable
200		if newline {
201			return fmt.Println(str)
202		}
203
204		return fmt.Print(str)
205	}
206
207	// fmt.Print("attribute val: ", attribute, "\n")
208	setConsoleTextAttr(uintptr(syscall.Stdout), attribute)
209
210	if newline {
211		fmt.Println(str)
212	} else {
213		fmt.Print(str)
214	}
215
216	// handle, _, _ = procSetTextAttribute.Call(uintptr(syscall.Stdout), winDefSetting)
217	// closeHandle := kernel32.NewProc("CloseHandle")
218	// closeHandle.Call(handle)
219
220	return winReset()
221}
222
223// func winRender(str string, colors ...Color) string {
224// 	setConsoleTextAttr(uintptr(syscall.Stdout), convertColorsToWinAttr(colors))
225//
226// 	return str
227// }
228
229// winSet set console color attributes
230func winSet(colors ...Color) (int, error) {
231	if !Enable { // not enable
232		return 0, nil
233	}
234
235	return setConsoleTextAttr(uintptr(syscall.Stdout), convertColorsToWinAttr(colors))
236}
237
238// winReset reset color settings to default
239func winReset() (int, error) {
240	// not enable
241	if !Enable {
242		return 0, nil
243	}
244
245	return setConsoleTextAttr(uintptr(syscall.Stdout), winDefSetting)
246}
247
248// convertColorsToWinAttr convert generic colors to win-colors attribute
249func convertColorsToWinAttr(colors []Color) uint16 {
250	var setting uint16
251	for _, c := range colors {
252		// check exists
253		if wc, ok := winColorsMap[c]; ok {
254			setting |= wc
255		}
256	}
257
258	return setting
259}
260
261// getWinColor convert Color to win-color value
262func getWinColor(color Color) uint16 {
263	if wc, ok := winColorsMap[color]; ok {
264		return wc
265	}
266
267	return 0
268}
269
270// setConsoleTextAttr
271// ret != 0 is OK.
272func setConsoleTextAttr(consoleOutput uintptr, winAttr uint16) (n int, err error) {
273	ret, _, err := procSetTextAttribute.Call(consoleOutput, uintptr(winAttr))
274
275	return int(ret), err
276}
277
278// IsTty returns true if the given file descriptor is a terminal.
279func IsTty(fd uintptr) bool {
280	var st uint32
281	r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
282	return r != 0 && e == 0
283}
284
285// IsTerminal returns true if the given file descriptor is a terminal.
286// Usage:
287// 	fd := os.Stdout.Fd()
288// 	fd := uintptr(syscall.Stdout) // for windows
289// 	IsTerminal(fd)
290func IsTerminal(fd int) bool {
291	var st uint32
292	r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
293	return r != 0 && e == 0
294}
295
296// from package: golang.org/x/sys/windows
297type (
298	short int16
299	word  uint16
300
301	// coord cursor position coordinates
302	coord struct {
303		x short
304		y short
305	}
306
307	smallRect struct {
308		left   short
309		top    short
310		right  short
311		bottom short
312	}
313
314	// Used with GetConsoleScreenBuffer to retrieve information about a console
315	// screen buffer. See
316	// https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str
317	// for details.
318	consoleScreenBufferInfo struct {
319		size              coord
320		cursorPosition    coord
321		attributes        word // is windows color setting
322		window            smallRect
323		maximumWindowSize coord
324	}
325)
326
327// GetSize returns the dimensions of the given terminal.
328func getSize(fd int) (width, height int, err error) {
329	var info consoleScreenBufferInfo
330	if err := getConsoleScreenBufferInfo(uintptr(fd), &info); err != nil {
331		return 0, 0, err
332	}
333
334	return int(info.size.x), int(info.size.y), nil
335}
336
337// from package: golang.org/x/sys/windows
338func getConsoleScreenBufferInfo(consoleOutput uintptr, info *consoleScreenBufferInfo) (err error) {
339	r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, consoleOutput, uintptr(unsafe.Pointer(info)), 0)
340	if r1 == 0 {
341		if e1 != 0 {
342			err = e1
343		} else {
344			err = syscall.EINVAL
345		}
346	}
347
348	return
349}
350