...

Source file src/github.com/mattn/go-colorable/colorable_windows.go

Documentation: github.com/mattn/go-colorable

     1  //go:build windows && !appengine
     2  // +build windows,!appengine
     3  
     4  package colorable
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"math"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  	"syscall"
    15  	"unsafe"
    16  
    17  	"github.com/mattn/go-isatty"
    18  )
    19  
    20  const (
    21  	foregroundBlue      = 0x1
    22  	foregroundGreen     = 0x2
    23  	foregroundRed       = 0x4
    24  	foregroundIntensity = 0x8
    25  	foregroundMask      = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
    26  	backgroundBlue      = 0x10
    27  	backgroundGreen     = 0x20
    28  	backgroundRed       = 0x40
    29  	backgroundIntensity = 0x80
    30  	backgroundMask      = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
    31  	commonLvbUnderscore = 0x8000
    32  
    33  	cENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
    34  )
    35  
    36  const (
    37  	genericRead  = 0x80000000
    38  	genericWrite = 0x40000000
    39  )
    40  
    41  const (
    42  	consoleTextmodeBuffer = 0x1
    43  )
    44  
    45  type wchar uint16
    46  type short int16
    47  type dword uint32
    48  type word uint16
    49  
    50  type coord struct {
    51  	x short
    52  	y short
    53  }
    54  
    55  type smallRect struct {
    56  	left   short
    57  	top    short
    58  	right  short
    59  	bottom short
    60  }
    61  
    62  type consoleScreenBufferInfo struct {
    63  	size              coord
    64  	cursorPosition    coord
    65  	attributes        word
    66  	window            smallRect
    67  	maximumWindowSize coord
    68  }
    69  
    70  type consoleCursorInfo struct {
    71  	size    dword
    72  	visible int32
    73  }
    74  
    75  var (
    76  	kernel32                       = syscall.NewLazyDLL("kernel32.dll")
    77  	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
    78  	procSetConsoleTextAttribute    = kernel32.NewProc("SetConsoleTextAttribute")
    79  	procSetConsoleCursorPosition   = kernel32.NewProc("SetConsoleCursorPosition")
    80  	procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
    81  	procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
    82  	procGetConsoleCursorInfo       = kernel32.NewProc("GetConsoleCursorInfo")
    83  	procSetConsoleCursorInfo       = kernel32.NewProc("SetConsoleCursorInfo")
    84  	procSetConsoleTitle            = kernel32.NewProc("SetConsoleTitleW")
    85  	procGetConsoleMode             = kernel32.NewProc("GetConsoleMode")
    86  	procSetConsoleMode             = kernel32.NewProc("SetConsoleMode")
    87  	procCreateConsoleScreenBuffer  = kernel32.NewProc("CreateConsoleScreenBuffer")
    88  )
    89  
    90  // Writer provides colorable Writer to the console
    91  type Writer struct {
    92  	out       io.Writer
    93  	handle    syscall.Handle
    94  	althandle syscall.Handle
    95  	oldattr   word
    96  	oldpos    coord
    97  	rest      bytes.Buffer
    98  	mutex     sync.Mutex
    99  }
   100  
   101  // NewColorable returns new instance of Writer which handles escape sequence from File.
   102  func NewColorable(file *os.File) io.Writer {
   103  	if file == nil {
   104  		panic("nil passed instead of *os.File to NewColorable()")
   105  	}
   106  
   107  	if isatty.IsTerminal(file.Fd()) {
   108  		var mode uint32
   109  		if r, _, _ := procGetConsoleMode.Call(file.Fd(), uintptr(unsafe.Pointer(&mode))); r != 0 && mode&cENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
   110  			return file
   111  		}
   112  		var csbi consoleScreenBufferInfo
   113  		handle := syscall.Handle(file.Fd())
   114  		procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   115  		return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
   116  	}
   117  	return file
   118  }
   119  
   120  // NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
   121  func NewColorableStdout() io.Writer {
   122  	return NewColorable(os.Stdout)
   123  }
   124  
   125  // NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
   126  func NewColorableStderr() io.Writer {
   127  	return NewColorable(os.Stderr)
   128  }
   129  
   130  var color256 = map[int]int{
   131  	0:   0x000000,
   132  	1:   0x800000,
   133  	2:   0x008000,
   134  	3:   0x808000,
   135  	4:   0x000080,
   136  	5:   0x800080,
   137  	6:   0x008080,
   138  	7:   0xc0c0c0,
   139  	8:   0x808080,
   140  	9:   0xff0000,
   141  	10:  0x00ff00,
   142  	11:  0xffff00,
   143  	12:  0x0000ff,
   144  	13:  0xff00ff,
   145  	14:  0x00ffff,
   146  	15:  0xffffff,
   147  	16:  0x000000,
   148  	17:  0x00005f,
   149  	18:  0x000087,
   150  	19:  0x0000af,
   151  	20:  0x0000d7,
   152  	21:  0x0000ff,
   153  	22:  0x005f00,
   154  	23:  0x005f5f,
   155  	24:  0x005f87,
   156  	25:  0x005faf,
   157  	26:  0x005fd7,
   158  	27:  0x005fff,
   159  	28:  0x008700,
   160  	29:  0x00875f,
   161  	30:  0x008787,
   162  	31:  0x0087af,
   163  	32:  0x0087d7,
   164  	33:  0x0087ff,
   165  	34:  0x00af00,
   166  	35:  0x00af5f,
   167  	36:  0x00af87,
   168  	37:  0x00afaf,
   169  	38:  0x00afd7,
   170  	39:  0x00afff,
   171  	40:  0x00d700,
   172  	41:  0x00d75f,
   173  	42:  0x00d787,
   174  	43:  0x00d7af,
   175  	44:  0x00d7d7,
   176  	45:  0x00d7ff,
   177  	46:  0x00ff00,
   178  	47:  0x00ff5f,
   179  	48:  0x00ff87,
   180  	49:  0x00ffaf,
   181  	50:  0x00ffd7,
   182  	51:  0x00ffff,
   183  	52:  0x5f0000,
   184  	53:  0x5f005f,
   185  	54:  0x5f0087,
   186  	55:  0x5f00af,
   187  	56:  0x5f00d7,
   188  	57:  0x5f00ff,
   189  	58:  0x5f5f00,
   190  	59:  0x5f5f5f,
   191  	60:  0x5f5f87,
   192  	61:  0x5f5faf,
   193  	62:  0x5f5fd7,
   194  	63:  0x5f5fff,
   195  	64:  0x5f8700,
   196  	65:  0x5f875f,
   197  	66:  0x5f8787,
   198  	67:  0x5f87af,
   199  	68:  0x5f87d7,
   200  	69:  0x5f87ff,
   201  	70:  0x5faf00,
   202  	71:  0x5faf5f,
   203  	72:  0x5faf87,
   204  	73:  0x5fafaf,
   205  	74:  0x5fafd7,
   206  	75:  0x5fafff,
   207  	76:  0x5fd700,
   208  	77:  0x5fd75f,
   209  	78:  0x5fd787,
   210  	79:  0x5fd7af,
   211  	80:  0x5fd7d7,
   212  	81:  0x5fd7ff,
   213  	82:  0x5fff00,
   214  	83:  0x5fff5f,
   215  	84:  0x5fff87,
   216  	85:  0x5fffaf,
   217  	86:  0x5fffd7,
   218  	87:  0x5fffff,
   219  	88:  0x870000,
   220  	89:  0x87005f,
   221  	90:  0x870087,
   222  	91:  0x8700af,
   223  	92:  0x8700d7,
   224  	93:  0x8700ff,
   225  	94:  0x875f00,
   226  	95:  0x875f5f,
   227  	96:  0x875f87,
   228  	97:  0x875faf,
   229  	98:  0x875fd7,
   230  	99:  0x875fff,
   231  	100: 0x878700,
   232  	101: 0x87875f,
   233  	102: 0x878787,
   234  	103: 0x8787af,
   235  	104: 0x8787d7,
   236  	105: 0x8787ff,
   237  	106: 0x87af00,
   238  	107: 0x87af5f,
   239  	108: 0x87af87,
   240  	109: 0x87afaf,
   241  	110: 0x87afd7,
   242  	111: 0x87afff,
   243  	112: 0x87d700,
   244  	113: 0x87d75f,
   245  	114: 0x87d787,
   246  	115: 0x87d7af,
   247  	116: 0x87d7d7,
   248  	117: 0x87d7ff,
   249  	118: 0x87ff00,
   250  	119: 0x87ff5f,
   251  	120: 0x87ff87,
   252  	121: 0x87ffaf,
   253  	122: 0x87ffd7,
   254  	123: 0x87ffff,
   255  	124: 0xaf0000,
   256  	125: 0xaf005f,
   257  	126: 0xaf0087,
   258  	127: 0xaf00af,
   259  	128: 0xaf00d7,
   260  	129: 0xaf00ff,
   261  	130: 0xaf5f00,
   262  	131: 0xaf5f5f,
   263  	132: 0xaf5f87,
   264  	133: 0xaf5faf,
   265  	134: 0xaf5fd7,
   266  	135: 0xaf5fff,
   267  	136: 0xaf8700,
   268  	137: 0xaf875f,
   269  	138: 0xaf8787,
   270  	139: 0xaf87af,
   271  	140: 0xaf87d7,
   272  	141: 0xaf87ff,
   273  	142: 0xafaf00,
   274  	143: 0xafaf5f,
   275  	144: 0xafaf87,
   276  	145: 0xafafaf,
   277  	146: 0xafafd7,
   278  	147: 0xafafff,
   279  	148: 0xafd700,
   280  	149: 0xafd75f,
   281  	150: 0xafd787,
   282  	151: 0xafd7af,
   283  	152: 0xafd7d7,
   284  	153: 0xafd7ff,
   285  	154: 0xafff00,
   286  	155: 0xafff5f,
   287  	156: 0xafff87,
   288  	157: 0xafffaf,
   289  	158: 0xafffd7,
   290  	159: 0xafffff,
   291  	160: 0xd70000,
   292  	161: 0xd7005f,
   293  	162: 0xd70087,
   294  	163: 0xd700af,
   295  	164: 0xd700d7,
   296  	165: 0xd700ff,
   297  	166: 0xd75f00,
   298  	167: 0xd75f5f,
   299  	168: 0xd75f87,
   300  	169: 0xd75faf,
   301  	170: 0xd75fd7,
   302  	171: 0xd75fff,
   303  	172: 0xd78700,
   304  	173: 0xd7875f,
   305  	174: 0xd78787,
   306  	175: 0xd787af,
   307  	176: 0xd787d7,
   308  	177: 0xd787ff,
   309  	178: 0xd7af00,
   310  	179: 0xd7af5f,
   311  	180: 0xd7af87,
   312  	181: 0xd7afaf,
   313  	182: 0xd7afd7,
   314  	183: 0xd7afff,
   315  	184: 0xd7d700,
   316  	185: 0xd7d75f,
   317  	186: 0xd7d787,
   318  	187: 0xd7d7af,
   319  	188: 0xd7d7d7,
   320  	189: 0xd7d7ff,
   321  	190: 0xd7ff00,
   322  	191: 0xd7ff5f,
   323  	192: 0xd7ff87,
   324  	193: 0xd7ffaf,
   325  	194: 0xd7ffd7,
   326  	195: 0xd7ffff,
   327  	196: 0xff0000,
   328  	197: 0xff005f,
   329  	198: 0xff0087,
   330  	199: 0xff00af,
   331  	200: 0xff00d7,
   332  	201: 0xff00ff,
   333  	202: 0xff5f00,
   334  	203: 0xff5f5f,
   335  	204: 0xff5f87,
   336  	205: 0xff5faf,
   337  	206: 0xff5fd7,
   338  	207: 0xff5fff,
   339  	208: 0xff8700,
   340  	209: 0xff875f,
   341  	210: 0xff8787,
   342  	211: 0xff87af,
   343  	212: 0xff87d7,
   344  	213: 0xff87ff,
   345  	214: 0xffaf00,
   346  	215: 0xffaf5f,
   347  	216: 0xffaf87,
   348  	217: 0xffafaf,
   349  	218: 0xffafd7,
   350  	219: 0xffafff,
   351  	220: 0xffd700,
   352  	221: 0xffd75f,
   353  	222: 0xffd787,
   354  	223: 0xffd7af,
   355  	224: 0xffd7d7,
   356  	225: 0xffd7ff,
   357  	226: 0xffff00,
   358  	227: 0xffff5f,
   359  	228: 0xffff87,
   360  	229: 0xffffaf,
   361  	230: 0xffffd7,
   362  	231: 0xffffff,
   363  	232: 0x080808,
   364  	233: 0x121212,
   365  	234: 0x1c1c1c,
   366  	235: 0x262626,
   367  	236: 0x303030,
   368  	237: 0x3a3a3a,
   369  	238: 0x444444,
   370  	239: 0x4e4e4e,
   371  	240: 0x585858,
   372  	241: 0x626262,
   373  	242: 0x6c6c6c,
   374  	243: 0x767676,
   375  	244: 0x808080,
   376  	245: 0x8a8a8a,
   377  	246: 0x949494,
   378  	247: 0x9e9e9e,
   379  	248: 0xa8a8a8,
   380  	249: 0xb2b2b2,
   381  	250: 0xbcbcbc,
   382  	251: 0xc6c6c6,
   383  	252: 0xd0d0d0,
   384  	253: 0xdadada,
   385  	254: 0xe4e4e4,
   386  	255: 0xeeeeee,
   387  }
   388  
   389  // `\033]0;TITLESTR\007`
   390  func doTitleSequence(er *bytes.Reader) error {
   391  	var c byte
   392  	var err error
   393  
   394  	c, err = er.ReadByte()
   395  	if err != nil {
   396  		return err
   397  	}
   398  	if c != '0' && c != '2' {
   399  		return nil
   400  	}
   401  	c, err = er.ReadByte()
   402  	if err != nil {
   403  		return err
   404  	}
   405  	if c != ';' {
   406  		return nil
   407  	}
   408  	title := make([]byte, 0, 80)
   409  	for {
   410  		c, err = er.ReadByte()
   411  		if err != nil {
   412  			return err
   413  		}
   414  		if c == 0x07 || c == '\n' {
   415  			break
   416  		}
   417  		title = append(title, c)
   418  	}
   419  	if len(title) > 0 {
   420  		title8, err := syscall.UTF16PtrFromString(string(title))
   421  		if err == nil {
   422  			procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
   423  		}
   424  	}
   425  	return nil
   426  }
   427  
   428  // returns Atoi(s) unless s == "" in which case it returns def
   429  func atoiWithDefault(s string, def int) (int, error) {
   430  	if s == "" {
   431  		return def, nil
   432  	}
   433  	return strconv.Atoi(s)
   434  }
   435  
   436  // Write writes data on console
   437  func (w *Writer) Write(data []byte) (n int, err error) {
   438  	w.mutex.Lock()
   439  	defer w.mutex.Unlock()
   440  	var csbi consoleScreenBufferInfo
   441  	procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
   442  
   443  	handle := w.handle
   444  
   445  	var er *bytes.Reader
   446  	if w.rest.Len() > 0 {
   447  		var rest bytes.Buffer
   448  		w.rest.WriteTo(&rest)
   449  		w.rest.Reset()
   450  		rest.Write(data)
   451  		er = bytes.NewReader(rest.Bytes())
   452  	} else {
   453  		er = bytes.NewReader(data)
   454  	}
   455  	var plaintext bytes.Buffer
   456  loop:
   457  	for {
   458  		c1, err := er.ReadByte()
   459  		if err != nil {
   460  			plaintext.WriteTo(w.out)
   461  			break loop
   462  		}
   463  		if c1 != 0x1b {
   464  			plaintext.WriteByte(c1)
   465  			continue
   466  		}
   467  		_, err = plaintext.WriteTo(w.out)
   468  		if err != nil {
   469  			break loop
   470  		}
   471  		c2, err := er.ReadByte()
   472  		if err != nil {
   473  			break loop
   474  		}
   475  
   476  		switch c2 {
   477  		case '>':
   478  			continue
   479  		case ']':
   480  			w.rest.WriteByte(c1)
   481  			w.rest.WriteByte(c2)
   482  			er.WriteTo(&w.rest)
   483  			if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
   484  				break loop
   485  			}
   486  			er = bytes.NewReader(w.rest.Bytes()[2:])
   487  			err := doTitleSequence(er)
   488  			if err != nil {
   489  				break loop
   490  			}
   491  			w.rest.Reset()
   492  			continue
   493  		// https://github.com/mattn/go-colorable/issues/27
   494  		case '7':
   495  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   496  			w.oldpos = csbi.cursorPosition
   497  			continue
   498  		case '8':
   499  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
   500  			continue
   501  		case 0x5b:
   502  			// execute part after switch
   503  		default:
   504  			continue
   505  		}
   506  
   507  		w.rest.WriteByte(c1)
   508  		w.rest.WriteByte(c2)
   509  		er.WriteTo(&w.rest)
   510  
   511  		var buf bytes.Buffer
   512  		var m byte
   513  		for i, c := range w.rest.Bytes()[2:] {
   514  			if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
   515  				m = c
   516  				er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
   517  				w.rest.Reset()
   518  				break
   519  			}
   520  			buf.Write([]byte(string(c)))
   521  		}
   522  		if m == 0 {
   523  			break loop
   524  		}
   525  
   526  		switch m {
   527  		case 'A':
   528  			n, err = atoiWithDefault(buf.String(), 1)
   529  			if err != nil {
   530  				continue
   531  			}
   532  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   533  			csbi.cursorPosition.y -= short(n)
   534  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   535  		case 'B':
   536  			n, err = atoiWithDefault(buf.String(), 1)
   537  			if err != nil {
   538  				continue
   539  			}
   540  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   541  			csbi.cursorPosition.y += short(n)
   542  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   543  		case 'C':
   544  			n, err = atoiWithDefault(buf.String(), 1)
   545  			if err != nil {
   546  				continue
   547  			}
   548  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   549  			csbi.cursorPosition.x += short(n)
   550  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   551  		case 'D':
   552  			n, err = atoiWithDefault(buf.String(), 1)
   553  			if err != nil {
   554  				continue
   555  			}
   556  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   557  			csbi.cursorPosition.x -= short(n)
   558  			if csbi.cursorPosition.x < 0 {
   559  				csbi.cursorPosition.x = 0
   560  			}
   561  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   562  		case 'E':
   563  			n, err = strconv.Atoi(buf.String())
   564  			if err != nil {
   565  				continue
   566  			}
   567  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   568  			csbi.cursorPosition.x = 0
   569  			csbi.cursorPosition.y += short(n)
   570  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   571  		case 'F':
   572  			n, err = strconv.Atoi(buf.String())
   573  			if err != nil {
   574  				continue
   575  			}
   576  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   577  			csbi.cursorPosition.x = 0
   578  			csbi.cursorPosition.y -= short(n)
   579  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   580  		case 'G':
   581  			n, err = strconv.Atoi(buf.String())
   582  			if err != nil {
   583  				continue
   584  			}
   585  			if n < 1 {
   586  				n = 1
   587  			}
   588  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   589  			csbi.cursorPosition.x = short(n - 1)
   590  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   591  		case 'H', 'f':
   592  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   593  			if buf.Len() > 0 {
   594  				token := strings.Split(buf.String(), ";")
   595  				switch len(token) {
   596  				case 1:
   597  					n1, err := strconv.Atoi(token[0])
   598  					if err != nil {
   599  						continue
   600  					}
   601  					csbi.cursorPosition.y = short(n1 - 1)
   602  				case 2:
   603  					n1, err := strconv.Atoi(token[0])
   604  					if err != nil {
   605  						continue
   606  					}
   607  					n2, err := strconv.Atoi(token[1])
   608  					if err != nil {
   609  						continue
   610  					}
   611  					csbi.cursorPosition.x = short(n2 - 1)
   612  					csbi.cursorPosition.y = short(n1 - 1)
   613  				}
   614  			} else {
   615  				csbi.cursorPosition.y = 0
   616  			}
   617  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
   618  		case 'J':
   619  			n := 0
   620  			if buf.Len() > 0 {
   621  				n, err = strconv.Atoi(buf.String())
   622  				if err != nil {
   623  					continue
   624  				}
   625  			}
   626  			var count, written dword
   627  			var cursor coord
   628  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   629  			switch n {
   630  			case 0:
   631  				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
   632  				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
   633  			case 1:
   634  				cursor = coord{x: csbi.window.left, y: csbi.window.top}
   635  				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
   636  			case 2:
   637  				cursor = coord{x: csbi.window.left, y: csbi.window.top}
   638  				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
   639  			}
   640  			procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   641  			procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   642  		case 'K':
   643  			n := 0
   644  			if buf.Len() > 0 {
   645  				n, err = strconv.Atoi(buf.String())
   646  				if err != nil {
   647  					continue
   648  				}
   649  			}
   650  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   651  			var cursor coord
   652  			var count, written dword
   653  			switch n {
   654  			case 0:
   655  				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
   656  				count = dword(csbi.size.x - csbi.cursorPosition.x)
   657  			case 1:
   658  				cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
   659  				count = dword(csbi.size.x - csbi.cursorPosition.x)
   660  			case 2:
   661  				cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
   662  				count = dword(csbi.size.x)
   663  			}
   664  			procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   665  			procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   666  		case 'X':
   667  			n := 0
   668  			if buf.Len() > 0 {
   669  				n, err = strconv.Atoi(buf.String())
   670  				if err != nil {
   671  					continue
   672  				}
   673  			}
   674  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   675  			var cursor coord
   676  			var written dword
   677  			cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
   678  			procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   679  			procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
   680  		case 'm':
   681  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   682  			attr := csbi.attributes
   683  			cs := buf.String()
   684  			if cs == "" {
   685  				procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
   686  				continue
   687  			}
   688  			token := strings.Split(cs, ";")
   689  			for i := 0; i < len(token); i++ {
   690  				ns := token[i]
   691  				if n, err = strconv.Atoi(ns); err == nil {
   692  					switch {
   693  					case n == 0 || n == 100:
   694  						attr = w.oldattr
   695  					case n == 4:
   696  						attr |= commonLvbUnderscore
   697  					case (1 <= n && n <= 3) || n == 5:
   698  						attr |= foregroundIntensity
   699  					case n == 7 || n == 27:
   700  						attr =
   701  							(attr &^ (foregroundMask | backgroundMask)) |
   702  								((attr & foregroundMask) << 4) |
   703  								((attr & backgroundMask) >> 4)
   704  					case n == 22:
   705  						attr &^= foregroundIntensity
   706  					case n == 24:
   707  						attr &^= commonLvbUnderscore
   708  					case 30 <= n && n <= 37:
   709  						attr &= backgroundMask
   710  						if (n-30)&1 != 0 {
   711  							attr |= foregroundRed
   712  						}
   713  						if (n-30)&2 != 0 {
   714  							attr |= foregroundGreen
   715  						}
   716  						if (n-30)&4 != 0 {
   717  							attr |= foregroundBlue
   718  						}
   719  					case n == 38: // set foreground color.
   720  						if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
   721  							if n256, err := strconv.Atoi(token[i+2]); err == nil {
   722  								if n256foreAttr == nil {
   723  									n256setup()
   724  								}
   725  								attr &= backgroundMask
   726  								attr |= n256foreAttr[n256%len(n256foreAttr)]
   727  								i += 2
   728  							}
   729  						} else if len(token) == 5 && token[i+1] == "2" {
   730  							var r, g, b int
   731  							r, _ = strconv.Atoi(token[i+2])
   732  							g, _ = strconv.Atoi(token[i+3])
   733  							b, _ = strconv.Atoi(token[i+4])
   734  							i += 4
   735  							if r > 127 {
   736  								attr |= foregroundRed
   737  							}
   738  							if g > 127 {
   739  								attr |= foregroundGreen
   740  							}
   741  							if b > 127 {
   742  								attr |= foregroundBlue
   743  							}
   744  						} else {
   745  							attr = attr & (w.oldattr & backgroundMask)
   746  						}
   747  					case n == 39: // reset foreground color.
   748  						attr &= backgroundMask
   749  						attr |= w.oldattr & foregroundMask
   750  					case 40 <= n && n <= 47:
   751  						attr &= foregroundMask
   752  						if (n-40)&1 != 0 {
   753  							attr |= backgroundRed
   754  						}
   755  						if (n-40)&2 != 0 {
   756  							attr |= backgroundGreen
   757  						}
   758  						if (n-40)&4 != 0 {
   759  							attr |= backgroundBlue
   760  						}
   761  					case n == 48: // set background color.
   762  						if i < len(token)-2 && token[i+1] == "5" {
   763  							if n256, err := strconv.Atoi(token[i+2]); err == nil {
   764  								if n256backAttr == nil {
   765  									n256setup()
   766  								}
   767  								attr &= foregroundMask
   768  								attr |= n256backAttr[n256%len(n256backAttr)]
   769  								i += 2
   770  							}
   771  						} else if len(token) == 5 && token[i+1] == "2" {
   772  							var r, g, b int
   773  							r, _ = strconv.Atoi(token[i+2])
   774  							g, _ = strconv.Atoi(token[i+3])
   775  							b, _ = strconv.Atoi(token[i+4])
   776  							i += 4
   777  							if r > 127 {
   778  								attr |= backgroundRed
   779  							}
   780  							if g > 127 {
   781  								attr |= backgroundGreen
   782  							}
   783  							if b > 127 {
   784  								attr |= backgroundBlue
   785  							}
   786  						} else {
   787  							attr = attr & (w.oldattr & foregroundMask)
   788  						}
   789  					case n == 49: // reset foreground color.
   790  						attr &= foregroundMask
   791  						attr |= w.oldattr & backgroundMask
   792  					case 90 <= n && n <= 97:
   793  						attr = (attr & backgroundMask)
   794  						attr |= foregroundIntensity
   795  						if (n-90)&1 != 0 {
   796  							attr |= foregroundRed
   797  						}
   798  						if (n-90)&2 != 0 {
   799  							attr |= foregroundGreen
   800  						}
   801  						if (n-90)&4 != 0 {
   802  							attr |= foregroundBlue
   803  						}
   804  					case 100 <= n && n <= 107:
   805  						attr = (attr & foregroundMask)
   806  						attr |= backgroundIntensity
   807  						if (n-100)&1 != 0 {
   808  							attr |= backgroundRed
   809  						}
   810  						if (n-100)&2 != 0 {
   811  							attr |= backgroundGreen
   812  						}
   813  						if (n-100)&4 != 0 {
   814  							attr |= backgroundBlue
   815  						}
   816  					}
   817  					procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
   818  				}
   819  			}
   820  		case 'h':
   821  			var ci consoleCursorInfo
   822  			cs := buf.String()
   823  			if cs == "5>" {
   824  				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   825  				ci.visible = 0
   826  				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   827  			} else if cs == "?25" {
   828  				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   829  				ci.visible = 1
   830  				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   831  			} else if cs == "?1049" {
   832  				if w.althandle == 0 {
   833  					h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
   834  					w.althandle = syscall.Handle(h)
   835  					if w.althandle != 0 {
   836  						handle = w.althandle
   837  					}
   838  				}
   839  			}
   840  		case 'l':
   841  			var ci consoleCursorInfo
   842  			cs := buf.String()
   843  			if cs == "5>" {
   844  				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   845  				ci.visible = 1
   846  				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   847  			} else if cs == "?25" {
   848  				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   849  				ci.visible = 0
   850  				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
   851  			} else if cs == "?1049" {
   852  				if w.althandle != 0 {
   853  					syscall.CloseHandle(w.althandle)
   854  					w.althandle = 0
   855  					handle = w.handle
   856  				}
   857  			}
   858  		case 's':
   859  			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
   860  			w.oldpos = csbi.cursorPosition
   861  		case 'u':
   862  			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
   863  		}
   864  	}
   865  
   866  	return len(data), nil
   867  }
   868  
   869  type consoleColor struct {
   870  	rgb       int
   871  	red       bool
   872  	green     bool
   873  	blue      bool
   874  	intensity bool
   875  }
   876  
   877  func (c consoleColor) foregroundAttr() (attr word) {
   878  	if c.red {
   879  		attr |= foregroundRed
   880  	}
   881  	if c.green {
   882  		attr |= foregroundGreen
   883  	}
   884  	if c.blue {
   885  		attr |= foregroundBlue
   886  	}
   887  	if c.intensity {
   888  		attr |= foregroundIntensity
   889  	}
   890  	return
   891  }
   892  
   893  func (c consoleColor) backgroundAttr() (attr word) {
   894  	if c.red {
   895  		attr |= backgroundRed
   896  	}
   897  	if c.green {
   898  		attr |= backgroundGreen
   899  	}
   900  	if c.blue {
   901  		attr |= backgroundBlue
   902  	}
   903  	if c.intensity {
   904  		attr |= backgroundIntensity
   905  	}
   906  	return
   907  }
   908  
   909  var color16 = []consoleColor{
   910  	{0x000000, false, false, false, false},
   911  	{0x000080, false, false, true, false},
   912  	{0x008000, false, true, false, false},
   913  	{0x008080, false, true, true, false},
   914  	{0x800000, true, false, false, false},
   915  	{0x800080, true, false, true, false},
   916  	{0x808000, true, true, false, false},
   917  	{0xc0c0c0, true, true, true, false},
   918  	{0x808080, false, false, false, true},
   919  	{0x0000ff, false, false, true, true},
   920  	{0x00ff00, false, true, false, true},
   921  	{0x00ffff, false, true, true, true},
   922  	{0xff0000, true, false, false, true},
   923  	{0xff00ff, true, false, true, true},
   924  	{0xffff00, true, true, false, true},
   925  	{0xffffff, true, true, true, true},
   926  }
   927  
   928  type hsv struct {
   929  	h, s, v float32
   930  }
   931  
   932  func (a hsv) dist(b hsv) float32 {
   933  	dh := a.h - b.h
   934  	switch {
   935  	case dh > 0.5:
   936  		dh = 1 - dh
   937  	case dh < -0.5:
   938  		dh = -1 - dh
   939  	}
   940  	ds := a.s - b.s
   941  	dv := a.v - b.v
   942  	return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
   943  }
   944  
   945  func toHSV(rgb int) hsv {
   946  	r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
   947  		float32((rgb&0x00FF00)>>8)/256.0,
   948  		float32(rgb&0x0000FF)/256.0
   949  	min, max := minmax3f(r, g, b)
   950  	h := max - min
   951  	if h > 0 {
   952  		if max == r {
   953  			h = (g - b) / h
   954  			if h < 0 {
   955  				h += 6
   956  			}
   957  		} else if max == g {
   958  			h = 2 + (b-r)/h
   959  		} else {
   960  			h = 4 + (r-g)/h
   961  		}
   962  	}
   963  	h /= 6.0
   964  	s := max - min
   965  	if max != 0 {
   966  		s /= max
   967  	}
   968  	v := max
   969  	return hsv{h: h, s: s, v: v}
   970  }
   971  
   972  type hsvTable []hsv
   973  
   974  func toHSVTable(rgbTable []consoleColor) hsvTable {
   975  	t := make(hsvTable, len(rgbTable))
   976  	for i, c := range rgbTable {
   977  		t[i] = toHSV(c.rgb)
   978  	}
   979  	return t
   980  }
   981  
   982  func (t hsvTable) find(rgb int) consoleColor {
   983  	hsv := toHSV(rgb)
   984  	n := 7
   985  	l := float32(5.0)
   986  	for i, p := range t {
   987  		d := hsv.dist(p)
   988  		if d < l {
   989  			l, n = d, i
   990  		}
   991  	}
   992  	return color16[n]
   993  }
   994  
   995  func minmax3f(a, b, c float32) (min, max float32) {
   996  	if a < b {
   997  		if b < c {
   998  			return a, c
   999  		} else if a < c {
  1000  			return a, b
  1001  		} else {
  1002  			return c, b
  1003  		}
  1004  	} else {
  1005  		if a < c {
  1006  			return b, c
  1007  		} else if b < c {
  1008  			return b, a
  1009  		} else {
  1010  			return c, a
  1011  		}
  1012  	}
  1013  }
  1014  
  1015  var n256foreAttr []word
  1016  var n256backAttr []word
  1017  
  1018  func n256setup() {
  1019  	n256foreAttr = make([]word, 256)
  1020  	n256backAttr = make([]word, 256)
  1021  	t := toHSVTable(color16)
  1022  	for i, rgb := range color256 {
  1023  		c := t.find(rgb)
  1024  		n256foreAttr[i] = c.foregroundAttr()
  1025  		n256backAttr[i] = c.backgroundAttr()
  1026  	}
  1027  }
  1028  
  1029  // EnableColorsStdout enable colors if possible.
  1030  func EnableColorsStdout(enabled *bool) func() {
  1031  	var mode uint32
  1032  	h := os.Stdout.Fd()
  1033  	if r, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 {
  1034  		if r, _, _ = procSetConsoleMode.Call(h, uintptr(mode|cENABLE_VIRTUAL_TERMINAL_PROCESSING)); r != 0 {
  1035  			if enabled != nil {
  1036  				*enabled = true
  1037  			}
  1038  			return func() {
  1039  				procSetConsoleMode.Call(h, uintptr(mode))
  1040  			}
  1041  		}
  1042  	}
  1043  	if enabled != nil {
  1044  		*enabled = true
  1045  	}
  1046  	return func() {}
  1047  }
  1048  

View as plain text