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