1// +build windows 2 3// Copyright 2021 The TCell Authors 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use file except in compliance with the License. 7// You may obtain a copy of the license at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17package tcell 18 19import ( 20 "errors" 21 "fmt" 22 "os" 23 "strings" 24 "sync" 25 "syscall" 26 "unicode/utf16" 27 "unsafe" 28) 29 30type cScreen struct { 31 in syscall.Handle 32 out syscall.Handle 33 cancelflag syscall.Handle 34 scandone chan struct{} 35 evch chan Event 36 quit chan struct{} 37 curx int 38 cury int 39 style Style 40 clear bool 41 fini bool 42 vten bool 43 truecolor bool 44 running bool 45 46 w int 47 h int 48 49 oscreen consoleInfo 50 ocursor cursorInfo 51 oimode uint32 52 oomode uint32 53 cells CellBuffer 54 55 finiOnce sync.Once 56 57 mouseEnabled bool 58 wg sync.WaitGroup 59 stopQ chan struct{} 60 61 sync.Mutex 62} 63 64var winLock sync.Mutex 65 66var winPalette = []Color{ 67 ColorBlack, 68 ColorMaroon, 69 ColorGreen, 70 ColorNavy, 71 ColorOlive, 72 ColorPurple, 73 ColorTeal, 74 ColorSilver, 75 ColorGray, 76 ColorRed, 77 ColorLime, 78 ColorBlue, 79 ColorYellow, 80 ColorFuchsia, 81 ColorAqua, 82 ColorWhite, 83} 84 85var winColors = map[Color]Color{ 86 ColorBlack: ColorBlack, 87 ColorMaroon: ColorMaroon, 88 ColorGreen: ColorGreen, 89 ColorNavy: ColorNavy, 90 ColorOlive: ColorOlive, 91 ColorPurple: ColorPurple, 92 ColorTeal: ColorTeal, 93 ColorSilver: ColorSilver, 94 ColorGray: ColorGray, 95 ColorRed: ColorRed, 96 ColorLime: ColorLime, 97 ColorBlue: ColorBlue, 98 ColorYellow: ColorYellow, 99 ColorFuchsia: ColorFuchsia, 100 ColorAqua: ColorAqua, 101 ColorWhite: ColorWhite, 102} 103 104var ( 105 k32 = syscall.NewLazyDLL("kernel32.dll") 106 u32 = syscall.NewLazyDLL("user32.dll") 107) 108 109// We have to bring in the kernel32 and user32 DLLs directly, so we can get 110// access to some system calls that the core Go API lacks. 111// 112// Note that Windows appends some functions with W to indicate that wide 113// characters (Unicode) are in use. The documentation refers to them 114// without this suffix, as the resolution is made via preprocessor. 115var ( 116 procReadConsoleInput = k32.NewProc("ReadConsoleInputW") 117 procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects") 118 procCreateEvent = k32.NewProc("CreateEventW") 119 procSetEvent = k32.NewProc("SetEvent") 120 procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo") 121 procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo") 122 procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition") 123 procSetConsoleMode = k32.NewProc("SetConsoleMode") 124 procGetConsoleMode = k32.NewProc("GetConsoleMode") 125 procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo") 126 procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute") 127 procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW") 128 procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo") 129 procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize") 130 procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute") 131 procMessageBeep = u32.NewProc("MessageBeep") 132) 133 134const ( 135 w32Infinite = ^uintptr(0) 136 w32WaitObject0 = uintptr(0) 137) 138 139const ( 140 // VT100/XTerm escapes understood by the console 141 vtShowCursor = "\x1b[?25h" 142 vtHideCursor = "\x1b[?25l" 143 vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X 144 vtSgr0 = "\x1b[0m" 145 vtBold = "\x1b[1m" 146 vtUnderline = "\x1b[4m" 147 vtBlink = "\x1b[5m" // Not sure this is processed 148 vtReverse = "\x1b[7m" 149 vtSetFg = "\x1b[38;5;%dm" 150 vtSetBg = "\x1b[48;5;%dm" 151 vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB 152 vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB 153) 154 155// NewConsoleScreen returns a Screen for the Windows console associated 156// with the current process. The Screen makes use of the Windows Console 157// API to display content and read events. 158func NewConsoleScreen() (Screen, error) { 159 return &cScreen{}, nil 160} 161 162func (s *cScreen) Init() error { 163 s.evch = make(chan Event, 10) 164 s.quit = make(chan struct{}) 165 s.scandone = make(chan struct{}) 166 167 in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0) 168 if e != nil { 169 return e 170 } 171 s.in = in 172 out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0) 173 if e != nil { 174 syscall.Close(s.in) 175 return e 176 } 177 s.out = out 178 179 s.truecolor = true 180 181 // ConEmu handling of colors and scrolling when in terminal 182 // mode is extremely problematic at the best. The color 183 // palette will scroll even though characters do not, when 184 // emitting stuff for the last character. In the future we 185 // might change this to look at specific versions of ConEmu 186 // if they fix the bug. 187 if os.Getenv("ConEmuPID") != "" { 188 s.truecolor = false 189 } 190 switch os.Getenv("TCELL_TRUECOLOR") { 191 case "disable": 192 s.truecolor = false 193 case "enable": 194 s.truecolor = true 195 } 196 197 s.Lock() 198 199 s.curx = -1 200 s.cury = -1 201 s.style = StyleDefault 202 s.getCursorInfo(&s.ocursor) 203 s.getConsoleInfo(&s.oscreen) 204 s.getOutMode(&s.oomode) 205 s.getInMode(&s.oimode) 206 s.resize() 207 208 s.fini = false 209 s.setInMode(modeResizeEn | modeExtndFlg) 210 211 // 24-bit color is opt-in for now, because we can't figure out 212 // to make it work consistently. 213 if s.truecolor { 214 s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut) 215 var omode uint32 216 s.getOutMode(&omode) 217 if omode&modeVtOutput == modeVtOutput { 218 s.vten = true 219 } else { 220 s.truecolor = false 221 s.setOutMode(0) 222 } 223 } else { 224 s.setOutMode(0) 225 } 226 227 s.Unlock() 228 229 return s.engage() 230} 231 232func (s *cScreen) CharacterSet() string { 233 // We are always UTF-16LE on Windows 234 return "UTF-16LE" 235} 236 237func (s *cScreen) EnableMouse(...MouseFlags) { 238 s.Lock() 239 s.mouseEnabled = true 240 s.enableMouse(true) 241 s.Unlock() 242} 243 244func (s *cScreen) DisableMouse() { 245 s.Lock() 246 s.mouseEnabled = false 247 s.enableMouse(false) 248 s.Unlock() 249} 250 251func (s *cScreen) enableMouse(on bool) { 252 if on { 253 s.setInMode(modeResizeEn | modeMouseEn | modeExtndFlg) 254 } else { 255 s.setInMode(modeResizeEn | modeExtndFlg) 256 } 257} 258 259// Windows lacks bracketed paste (for now) 260 261func (s *cScreen) EnablePaste() {} 262 263func (s *cScreen) DisablePaste() {} 264 265func (s *cScreen) Fini() { 266 s.disengage() 267} 268 269func (s *cScreen) disengage() { 270 s.Lock() 271 if !s.running { 272 s.Unlock() 273 return 274 } 275 s.running = false 276 stopQ := s.stopQ 277 procSetEvent.Call(uintptr(s.cancelflag)) 278 close(stopQ) 279 s.Unlock() 280 281 s.wg.Wait() 282 283 s.setInMode(s.oimode) 284 s.setOutMode(s.oomode) 285 s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) 286 s.clearScreen(StyleDefault, false) 287 s.setCursorPos(0, 0, false) 288 s.setCursorInfo(&s.ocursor) 289 procSetConsoleTextAttribute.Call( 290 uintptr(s.out), 291 uintptr(s.mapStyle(StyleDefault))) 292} 293 294func (s *cScreen) engage() error { 295 s.Lock() 296 defer s.Unlock() 297 if s.running { 298 return errors.New("already engaged") 299 } 300 s.stopQ = make(chan struct{}) 301 cf, _, e := procCreateEvent.Call( 302 uintptr(0), 303 uintptr(1), 304 uintptr(0), 305 uintptr(0)) 306 if cf == uintptr(0) { 307 return e 308 } 309 s.running = true 310 s.cancelflag = syscall.Handle(cf) 311 s.enableMouse(s.mouseEnabled) 312 313 if s.vten { 314 s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut) 315 } else { 316 s.setOutMode(0) 317 } 318 319 s.clearScreen(s.style, s.vten) 320 s.hideCursor() 321 322 s.cells.Invalidate() 323 s.hideCursor() 324 s.resize() 325 s.draw() 326 s.doCursor() 327 328 s.wg.Add(1) 329 go s.scanInput(s.stopQ) 330 return nil 331} 332 333func (s *cScreen) PostEventWait(ev Event) { 334 s.evch <- ev 335} 336 337func (s *cScreen) PostEvent(ev Event) error { 338 select { 339 case s.evch <- ev: 340 return nil 341 default: 342 return ErrEventQFull 343 } 344} 345 346func (s *cScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) { 347 defer close(ch) 348 for { 349 select { 350 case <-quit: 351 return 352 case <-s.stopQ: 353 return 354 case ev := <-s.evch: 355 select { 356 case <-quit: 357 return 358 case <-s.stopQ: 359 return 360 case ch <- ev: 361 } 362 } 363 } 364} 365 366func (s *cScreen) PollEvent() Event { 367 select { 368 case <-s.stopQ: 369 return nil 370 case ev := <-s.evch: 371 return ev 372 } 373} 374 375func (s *cScreen) HasPendingEvent() bool { 376 return len(s.evch) > 0 377} 378 379type cursorInfo struct { 380 size uint32 381 visible uint32 382} 383 384type coord struct { 385 x int16 386 y int16 387} 388 389func (c coord) uintptr() uintptr { 390 // little endian, put x first 391 return uintptr(c.x) | (uintptr(c.y) << 16) 392} 393 394type rect struct { 395 left int16 396 top int16 397 right int16 398 bottom int16 399} 400 401func (s *cScreen) emitVtString(vs string) { 402 esc := utf16.Encode([]rune(vs)) 403 syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil) 404} 405 406func (s *cScreen) showCursor() { 407 if s.vten { 408 s.emitVtString(vtShowCursor) 409 } else { 410 s.setCursorInfo(&cursorInfo{size: 100, visible: 1}) 411 } 412} 413 414func (s *cScreen) hideCursor() { 415 if s.vten { 416 s.emitVtString(vtHideCursor) 417 } else { 418 s.setCursorInfo(&cursorInfo{size: 1, visible: 0}) 419 } 420} 421 422func (s *cScreen) ShowCursor(x, y int) { 423 s.Lock() 424 if !s.fini { 425 s.curx = x 426 s.cury = y 427 } 428 s.doCursor() 429 s.Unlock() 430} 431 432func (s *cScreen) doCursor() { 433 x, y := s.curx, s.cury 434 435 if x < 0 || y < 0 || x >= s.w || y >= s.h { 436 s.hideCursor() 437 } else { 438 s.setCursorPos(x, y, s.vten) 439 s.showCursor() 440 } 441} 442 443func (s *cScreen) HideCursor() { 444 s.ShowCursor(-1, -1) 445} 446 447type inputRecord struct { 448 typ uint16 449 _ uint16 450 data [16]byte 451} 452 453const ( 454 keyEvent uint16 = 1 455 mouseEvent uint16 = 2 456 resizeEvent uint16 = 4 457 menuEvent uint16 = 8 // don't use 458 focusEvent uint16 = 16 // don't use 459) 460 461type mouseRecord struct { 462 x int16 463 y int16 464 btns uint32 465 mod uint32 466 flags uint32 467} 468 469const ( 470 mouseDoubleClick uint32 = 0x2 471 mouseHWheeled uint32 = 0x8 472 mouseVWheeled uint32 = 0x4 473 mouseMoved uint32 = 0x1 474) 475 476type resizeRecord struct { 477 x int16 478 y int16 479} 480 481type keyRecord struct { 482 isdown int32 483 repeat uint16 484 kcode uint16 485 scode uint16 486 ch uint16 487 mod uint32 488} 489 490const ( 491 // Constants per Microsoft. We don't put the modifiers 492 // here. 493 vkCancel = 0x03 494 vkBack = 0x08 // Backspace 495 vkTab = 0x09 496 vkClear = 0x0c 497 vkReturn = 0x0d 498 vkPause = 0x13 499 vkEscape = 0x1b 500 vkSpace = 0x20 501 vkPrior = 0x21 // PgUp 502 vkNext = 0x22 // PgDn 503 vkEnd = 0x23 504 vkHome = 0x24 505 vkLeft = 0x25 506 vkUp = 0x26 507 vkRight = 0x27 508 vkDown = 0x28 509 vkPrint = 0x2a 510 vkPrtScr = 0x2c 511 vkInsert = 0x2d 512 vkDelete = 0x2e 513 vkHelp = 0x2f 514 vkF1 = 0x70 515 vkF2 = 0x71 516 vkF3 = 0x72 517 vkF4 = 0x73 518 vkF5 = 0x74 519 vkF6 = 0x75 520 vkF7 = 0x76 521 vkF8 = 0x77 522 vkF9 = 0x78 523 vkF10 = 0x79 524 vkF11 = 0x7a 525 vkF12 = 0x7b 526 vkF13 = 0x7c 527 vkF14 = 0x7d 528 vkF15 = 0x7e 529 vkF16 = 0x7f 530 vkF17 = 0x80 531 vkF18 = 0x81 532 vkF19 = 0x82 533 vkF20 = 0x83 534 vkF21 = 0x84 535 vkF22 = 0x85 536 vkF23 = 0x86 537 vkF24 = 0x87 538) 539 540var vkKeys = map[uint16]Key{ 541 vkCancel: KeyCancel, 542 vkBack: KeyBackspace, 543 vkTab: KeyTab, 544 vkClear: KeyClear, 545 vkPause: KeyPause, 546 vkPrint: KeyPrint, 547 vkPrtScr: KeyPrint, 548 vkPrior: KeyPgUp, 549 vkNext: KeyPgDn, 550 vkReturn: KeyEnter, 551 vkEnd: KeyEnd, 552 vkHome: KeyHome, 553 vkLeft: KeyLeft, 554 vkUp: KeyUp, 555 vkRight: KeyRight, 556 vkDown: KeyDown, 557 vkInsert: KeyInsert, 558 vkDelete: KeyDelete, 559 vkHelp: KeyHelp, 560 vkF1: KeyF1, 561 vkF2: KeyF2, 562 vkF3: KeyF3, 563 vkF4: KeyF4, 564 vkF5: KeyF5, 565 vkF6: KeyF6, 566 vkF7: KeyF7, 567 vkF8: KeyF8, 568 vkF9: KeyF9, 569 vkF10: KeyF10, 570 vkF11: KeyF11, 571 vkF12: KeyF12, 572 vkF13: KeyF13, 573 vkF14: KeyF14, 574 vkF15: KeyF15, 575 vkF16: KeyF16, 576 vkF17: KeyF17, 577 vkF18: KeyF18, 578 vkF19: KeyF19, 579 vkF20: KeyF20, 580 vkF21: KeyF21, 581 vkF22: KeyF22, 582 vkF23: KeyF23, 583 vkF24: KeyF24, 584} 585 586// NB: All Windows platforms are little endian. We assume this 587// never, ever change. The following code is endian safe. and does 588// not use unsafe pointers. 589func getu32(v []byte) uint32 { 590 return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24) 591} 592func geti32(v []byte) int32 { 593 return int32(getu32(v)) 594} 595func getu16(v []byte) uint16 { 596 return uint16(v[0]) + (uint16(v[1]) << 8) 597} 598func geti16(v []byte) int16 { 599 return int16(getu16(v)) 600} 601 602// Convert windows dwControlKeyState to modifier mask 603func mod2mask(cks uint32) ModMask { 604 mm := ModNone 605 // Left or right control 606 if (cks & (0x0008 | 0x0004)) != 0 { 607 mm |= ModCtrl 608 } 609 // Left or right alt 610 if (cks & (0x0002 | 0x0001)) != 0 { 611 mm |= ModAlt 612 } 613 // Any shift 614 if (cks & 0x0010) != 0 { 615 mm |= ModShift 616 } 617 return mm 618} 619 620func mrec2btns(mbtns, flags uint32) ButtonMask { 621 btns := ButtonNone 622 if mbtns&0x1 != 0 { 623 btns |= Button1 624 } 625 if mbtns&0x2 != 0 { 626 btns |= Button2 627 } 628 if mbtns&0x4 != 0 { 629 btns |= Button3 630 } 631 if mbtns&0x8 != 0 { 632 btns |= Button4 633 } 634 if mbtns&0x10 != 0 { 635 btns |= Button5 636 } 637 if mbtns&0x20 != 0 { 638 btns |= Button6 639 } 640 if mbtns&0x40 != 0 { 641 btns |= Button7 642 } 643 if mbtns&0x80 != 0 { 644 btns |= Button8 645 } 646 647 if flags&mouseVWheeled != 0 { 648 if mbtns&0x80000000 == 0 { 649 btns |= WheelUp 650 } else { 651 btns |= WheelDown 652 } 653 } 654 if flags&mouseHWheeled != 0 { 655 if mbtns&0x80000000 == 0 { 656 btns |= WheelRight 657 } else { 658 btns |= WheelLeft 659 } 660 } 661 return btns 662} 663 664func (s *cScreen) getConsoleInput() error { 665 // cancelFlag comes first as WaitForMultipleObjects returns the lowest index 666 // in the event that both events are signalled. 667 waitObjects := []syscall.Handle{s.cancelflag, s.in} 668 // As arrays are contiguous in memory, a pointer to the first object is the 669 // same as a pointer to the array itself. 670 pWaitObjects := unsafe.Pointer(&waitObjects[0]) 671 672 rv, _, er := procWaitForMultipleObjects.Call( 673 uintptr(len(waitObjects)), 674 uintptr(pWaitObjects), 675 uintptr(0), 676 w32Infinite) 677 // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index. 678 switch rv { 679 case w32WaitObject0: // s.cancelFlag 680 return errors.New("cancelled") 681 case w32WaitObject0 + 1: // s.in 682 rec := &inputRecord{} 683 var nrec int32 684 rv, _, er := procReadConsoleInput.Call( 685 uintptr(s.in), 686 uintptr(unsafe.Pointer(rec)), 687 uintptr(1), 688 uintptr(unsafe.Pointer(&nrec))) 689 if rv == 0 { 690 return er 691 } 692 if nrec != 1 { 693 return nil 694 } 695 switch rec.typ { 696 case keyEvent: 697 krec := &keyRecord{} 698 krec.isdown = geti32(rec.data[0:]) 699 krec.repeat = getu16(rec.data[4:]) 700 krec.kcode = getu16(rec.data[6:]) 701 krec.scode = getu16(rec.data[8:]) 702 krec.ch = getu16(rec.data[10:]) 703 krec.mod = getu32(rec.data[12:]) 704 705 if krec.isdown == 0 || krec.repeat < 1 { 706 // its a key release event, ignore it 707 return nil 708 } 709 if krec.ch != 0 { 710 // synthesized key code 711 for krec.repeat > 0 { 712 // convert shift+tab to backtab 713 if mod2mask(krec.mod) == ModShift && krec.ch == vkTab { 714 s.PostEventWait(NewEventKey(KeyBacktab, 0, 715 ModNone)) 716 } else { 717 s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch), 718 mod2mask(krec.mod))) 719 } 720 krec.repeat-- 721 } 722 return nil 723 } 724 key := KeyNUL // impossible on Windows 725 ok := false 726 if key, ok = vkKeys[krec.kcode]; !ok { 727 return nil 728 } 729 for krec.repeat > 0 { 730 s.PostEventWait(NewEventKey(key, rune(krec.ch), 731 mod2mask(krec.mod))) 732 krec.repeat-- 733 } 734 735 case mouseEvent: 736 var mrec mouseRecord 737 mrec.x = geti16(rec.data[0:]) 738 mrec.y = geti16(rec.data[2:]) 739 mrec.btns = getu32(rec.data[4:]) 740 mrec.mod = getu32(rec.data[8:]) 741 mrec.flags = getu32(rec.data[12:]) 742 btns := mrec2btns(mrec.btns, mrec.flags) 743 // we ignore double click, events are delivered normally 744 s.PostEventWait(NewEventMouse(int(mrec.x), int(mrec.y), btns, 745 mod2mask(mrec.mod))) 746 747 case resizeEvent: 748 var rrec resizeRecord 749 rrec.x = geti16(rec.data[0:]) 750 rrec.y = geti16(rec.data[2:]) 751 s.PostEventWait(NewEventResize(int(rrec.x), int(rrec.y))) 752 753 default: 754 } 755 default: 756 return er 757 } 758 759 return nil 760} 761 762func (s *cScreen) scanInput(stopQ chan struct{}) { 763 defer s.wg.Done() 764 for { 765 select { 766 case <-stopQ: 767 return 768 default: 769 } 770 if e := s.getConsoleInput(); e != nil { 771 return 772 } 773 } 774} 775 776// Windows console can display 8 characters, in either low or high intensity 777func (s *cScreen) Colors() int { 778 if s.vten { 779 return 1 << 24 780 } 781 return 16 782} 783 784var vgaColors = map[Color]uint16{ 785 ColorBlack: 0, 786 ColorMaroon: 0x4, 787 ColorGreen: 0x2, 788 ColorNavy: 0x1, 789 ColorOlive: 0x6, 790 ColorPurple: 0x5, 791 ColorTeal: 0x3, 792 ColorSilver: 0x7, 793 ColorGrey: 0x8, 794 ColorRed: 0xc, 795 ColorLime: 0xa, 796 ColorBlue: 0x9, 797 ColorYellow: 0xe, 798 ColorFuchsia: 0xd, 799 ColorAqua: 0xb, 800 ColorWhite: 0xf, 801} 802 803// Windows uses RGB signals 804func mapColor2RGB(c Color) uint16 { 805 winLock.Lock() 806 if v, ok := winColors[c]; ok { 807 c = v 808 } else { 809 v = FindColor(c, winPalette) 810 winColors[c] = v 811 c = v 812 } 813 winLock.Unlock() 814 815 if vc, ok := vgaColors[c]; ok { 816 return vc 817 } 818 return 0 819} 820 821// Map a tcell style to Windows attributes 822func (s *cScreen) mapStyle(style Style) uint16 { 823 f, b, a := style.Decompose() 824 fa := s.oscreen.attrs & 0xf 825 ba := (s.oscreen.attrs) >> 4 & 0xf 826 if f != ColorDefault && f != ColorReset { 827 fa = mapColor2RGB(f) 828 } 829 if b != ColorDefault && b != ColorReset { 830 ba = mapColor2RGB(b) 831 } 832 var attr uint16 833 // We simulate reverse by doing the color swap ourselves. 834 // Apparently windows cannot really do this except in DBCS 835 // views. 836 if a&AttrReverse != 0 { 837 attr = ba 838 attr |= (fa << 4) 839 } else { 840 attr = fa 841 attr |= (ba << 4) 842 } 843 if a&AttrBold != 0 { 844 attr |= 0x8 845 } 846 if a&AttrDim != 0 { 847 attr &^= 0x8 848 } 849 if a&AttrUnderline != 0 { 850 // Best effort -- doesn't seem to work though. 851 attr |= 0x8000 852 } 853 // Blink is unsupported 854 return attr 855} 856 857func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) { 858 if len(ch) > 0 { 859 s.SetContent(x, y, ch[0], ch[1:], style) 860 } else { 861 s.SetContent(x, y, ' ', nil, style) 862 } 863} 864 865func (s *cScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) { 866 s.Lock() 867 if !s.fini { 868 s.cells.SetContent(x, y, mainc, combc, style) 869 } 870 s.Unlock() 871} 872 873func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) { 874 s.Lock() 875 mainc, combc, style, width := s.cells.GetContent(x, y) 876 s.Unlock() 877 return mainc, combc, style, width 878} 879 880func (s *cScreen) sendVtStyle(style Style) { 881 esc := &strings.Builder{} 882 883 fg, bg, attrs := style.Decompose() 884 885 esc.WriteString(vtSgr0) 886 887 if attrs&(AttrBold|AttrDim) == AttrBold { 888 esc.WriteString(vtBold) 889 } 890 if attrs&AttrBlink != 0 { 891 esc.WriteString(vtBlink) 892 } 893 if attrs&AttrUnderline != 0 { 894 esc.WriteString(vtUnderline) 895 } 896 if attrs&AttrReverse != 0 { 897 esc.WriteString(vtReverse) 898 } 899 if fg.IsRGB() { 900 r, g, b := fg.RGB() 901 fmt.Fprintf(esc, vtSetFgRGB, r, g, b) 902 } else if fg.Valid() { 903 fmt.Fprintf(esc, vtSetFg, fg&0xff) 904 } 905 if bg.IsRGB() { 906 r, g, b := bg.RGB() 907 fmt.Fprintf(esc, vtSetBgRGB, r, g, b) 908 } else if bg.Valid() { 909 fmt.Fprintf(esc, vtSetBg, bg&0xff) 910 } 911 s.emitVtString(esc.String()) 912} 913 914func (s *cScreen) writeString(x, y int, style Style, ch []uint16) { 915 // we assume the caller has hidden the cursor 916 if len(ch) == 0 { 917 return 918 } 919 s.setCursorPos(x, y, s.vten) 920 921 if s.vten { 922 s.sendVtStyle(style) 923 } else { 924 procSetConsoleTextAttribute.Call( 925 uintptr(s.out), 926 uintptr(s.mapStyle(style))) 927 } 928 syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil) 929} 930 931func (s *cScreen) draw() { 932 // allocate a scratch line bit enough for no combining chars. 933 // if you have combining characters, you may pay for extra allocs. 934 if s.clear { 935 s.clearScreen(s.style, s.vten) 936 s.clear = false 937 s.cells.Invalidate() 938 } 939 buf := make([]uint16, 0, s.w) 940 wcs := buf[:] 941 lstyle := styleInvalid 942 943 lx, ly := -1, -1 944 ra := make([]rune, 1) 945 946 for y := 0; y < s.h; y++ { 947 for x := 0; x < s.w; x++ { 948 mainc, combc, style, width := s.cells.GetContent(x, y) 949 dirty := s.cells.Dirty(x, y) 950 if style == StyleDefault { 951 style = s.style 952 } 953 954 if !dirty || style != lstyle { 955 // write out any data queued thus far 956 // because we are going to skip over some 957 // cells, or because we need to change styles 958 s.writeString(lx, ly, lstyle, wcs) 959 wcs = buf[0:0] 960 lstyle = StyleDefault 961 if !dirty { 962 continue 963 } 964 } 965 if x > s.w-width { 966 mainc = ' ' 967 combc = nil 968 width = 1 969 } 970 if len(wcs) == 0 { 971 lstyle = style 972 lx = x 973 ly = y 974 } 975 ra[0] = mainc 976 wcs = append(wcs, utf16.Encode(ra)...) 977 if len(combc) != 0 { 978 wcs = append(wcs, utf16.Encode(combc)...) 979 } 980 for dx := 0; dx < width; dx++ { 981 s.cells.SetDirty(x+dx, y, false) 982 } 983 x += width - 1 984 } 985 s.writeString(lx, ly, lstyle, wcs) 986 wcs = buf[0:0] 987 lstyle = styleInvalid 988 } 989} 990 991func (s *cScreen) Show() { 992 s.Lock() 993 if !s.fini { 994 s.hideCursor() 995 s.resize() 996 s.draw() 997 s.doCursor() 998 } 999 s.Unlock() 1000} 1001 1002func (s *cScreen) Sync() { 1003 s.Lock() 1004 if !s.fini { 1005 s.cells.Invalidate() 1006 s.hideCursor() 1007 s.resize() 1008 s.draw() 1009 s.doCursor() 1010 } 1011 s.Unlock() 1012} 1013 1014type consoleInfo struct { 1015 size coord 1016 pos coord 1017 attrs uint16 1018 win rect 1019 maxsz coord 1020} 1021 1022func (s *cScreen) getConsoleInfo(info *consoleInfo) { 1023 procGetConsoleScreenBufferInfo.Call( 1024 uintptr(s.out), 1025 uintptr(unsafe.Pointer(info))) 1026} 1027 1028func (s *cScreen) getCursorInfo(info *cursorInfo) { 1029 procGetConsoleCursorInfo.Call( 1030 uintptr(s.out), 1031 uintptr(unsafe.Pointer(info))) 1032} 1033 1034func (s *cScreen) setCursorInfo(info *cursorInfo) { 1035 procSetConsoleCursorInfo.Call( 1036 uintptr(s.out), 1037 uintptr(unsafe.Pointer(info))) 1038 1039} 1040 1041func (s *cScreen) setCursorPos(x, y int, vtEnable bool) { 1042 if vtEnable { 1043 // Note that the string is Y first. Origin is 1,1. 1044 s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1)) 1045 } else { 1046 procSetConsoleCursorPosition.Call( 1047 uintptr(s.out), 1048 coord{int16(x), int16(y)}.uintptr()) 1049 } 1050} 1051 1052func (s *cScreen) setBufferSize(x, y int) { 1053 procSetConsoleScreenBufferSize.Call( 1054 uintptr(s.out), 1055 coord{int16(x), int16(y)}.uintptr()) 1056} 1057 1058func (s *cScreen) Size() (int, int) { 1059 s.Lock() 1060 w, h := s.w, s.h 1061 s.Unlock() 1062 1063 return w, h 1064} 1065 1066func (s *cScreen) resize() { 1067 info := consoleInfo{} 1068 s.getConsoleInfo(&info) 1069 1070 w := int((info.win.right - info.win.left) + 1) 1071 h := int((info.win.bottom - info.win.top) + 1) 1072 1073 if s.w == w && s.h == h { 1074 return 1075 } 1076 1077 s.cells.Resize(w, h) 1078 s.w = w 1079 s.h = h 1080 1081 s.setBufferSize(w, h) 1082 1083 r := rect{0, 0, int16(w - 1), int16(h - 1)} 1084 procSetConsoleWindowInfo.Call( 1085 uintptr(s.out), 1086 uintptr(1), 1087 uintptr(unsafe.Pointer(&r))) 1088 s.PostEvent(NewEventResize(w, h)) 1089} 1090 1091func (s *cScreen) Clear() { 1092 s.Fill(' ', s.style) 1093} 1094 1095func (s *cScreen) Fill(r rune, style Style) { 1096 s.Lock() 1097 if !s.fini { 1098 s.cells.Fill(r, style) 1099 s.clear = true 1100 } 1101 s.Unlock() 1102} 1103 1104func (s *cScreen) clearScreen(style Style, vtEnable bool) { 1105 if vtEnable { 1106 s.sendVtStyle(style) 1107 row := strings.Repeat(" ", s.w) 1108 for y := 0; y < s.h; y++ { 1109 s.setCursorPos(0, y, vtEnable) 1110 s.emitVtString(row) 1111 } 1112 s.setCursorPos(0, 0, vtEnable) 1113 1114 } else { 1115 pos := coord{0, 0} 1116 attr := s.mapStyle(style) 1117 x, y := s.w, s.h 1118 scratch := uint32(0) 1119 count := uint32(x * y) 1120 1121 procFillConsoleOutputAttribute.Call( 1122 uintptr(s.out), 1123 uintptr(attr), 1124 uintptr(count), 1125 pos.uintptr(), 1126 uintptr(unsafe.Pointer(&scratch))) 1127 procFillConsoleOutputCharacter.Call( 1128 uintptr(s.out), 1129 uintptr(' '), 1130 uintptr(count), 1131 pos.uintptr(), 1132 uintptr(unsafe.Pointer(&scratch))) 1133 } 1134} 1135 1136const ( 1137 // Input modes 1138 modeExtndFlg uint32 = 0x0080 1139 modeMouseEn = 0x0010 1140 modeResizeEn = 0x0008 1141 modeCooked = 0x0001 1142 modeVtInput = 0x0200 1143 1144 // Output modes 1145 modeCookedOut uint32 = 0x0001 1146 modeWrapEOL = 0x0002 1147 modeVtOutput = 0x0004 1148 modeNoAutoNL = 0x0008 1149) 1150 1151func (s *cScreen) setInMode(mode uint32) error { 1152 rv, _, err := procSetConsoleMode.Call( 1153 uintptr(s.in), 1154 uintptr(mode)) 1155 if rv == 0 { 1156 return err 1157 } 1158 return nil 1159} 1160 1161func (s *cScreen) setOutMode(mode uint32) error { 1162 rv, _, err := procSetConsoleMode.Call( 1163 uintptr(s.out), 1164 uintptr(mode)) 1165 if rv == 0 { 1166 return err 1167 } 1168 return nil 1169} 1170 1171func (s *cScreen) getInMode(v *uint32) { 1172 procGetConsoleMode.Call( 1173 uintptr(s.in), 1174 uintptr(unsafe.Pointer(v))) 1175} 1176 1177func (s *cScreen) getOutMode(v *uint32) { 1178 procGetConsoleMode.Call( 1179 uintptr(s.out), 1180 uintptr(unsafe.Pointer(v))) 1181} 1182 1183func (s *cScreen) SetStyle(style Style) { 1184 s.Lock() 1185 s.style = style 1186 s.Unlock() 1187} 1188 1189// No fallback rune support, since we have Unicode. Yay! 1190 1191func (s *cScreen) RegisterRuneFallback(r rune, subst string) { 1192} 1193 1194func (s *cScreen) UnregisterRuneFallback(r rune) { 1195} 1196 1197func (s *cScreen) CanDisplay(r rune, checkFallbacks bool) bool { 1198 // We presume we can display anything -- we're Unicode. 1199 // (Sadly this not precisely true. Combinings are especially 1200 // poorly supported under Windows.) 1201 return true 1202} 1203 1204func (s *cScreen) HasMouse() bool { 1205 return true 1206} 1207 1208func (s *cScreen) Resize(int, int, int, int) {} 1209 1210func (s *cScreen) HasKey(k Key) bool { 1211 // Microsoft has codes for some keys, but they are unusual, 1212 // so we don't include them. We include all the typical 1213 // 101, 105 key layout keys. 1214 valid := map[Key]bool{ 1215 KeyBackspace: true, 1216 KeyTab: true, 1217 KeyEscape: true, 1218 KeyPause: true, 1219 KeyPrint: true, 1220 KeyPgUp: true, 1221 KeyPgDn: true, 1222 KeyEnter: true, 1223 KeyEnd: true, 1224 KeyHome: true, 1225 KeyLeft: true, 1226 KeyUp: true, 1227 KeyRight: true, 1228 KeyDown: true, 1229 KeyInsert: true, 1230 KeyDelete: true, 1231 KeyF1: true, 1232 KeyF2: true, 1233 KeyF3: true, 1234 KeyF4: true, 1235 KeyF5: true, 1236 KeyF6: true, 1237 KeyF7: true, 1238 KeyF8: true, 1239 KeyF9: true, 1240 KeyF10: true, 1241 KeyF11: true, 1242 KeyF12: true, 1243 KeyRune: true, 1244 } 1245 1246 return valid[k] 1247} 1248 1249func (s *cScreen) Beep() error { 1250 // A simple beep. If the sound card is not available, the sound is generated 1251 // using the speaker. 1252 // 1253 // Reference: 1254 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep 1255 const simpleBeep = 0xffffffff 1256 if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 { 1257 return err 1258 } 1259 return nil 1260} 1261 1262func (s *cScreen) Suspend() error { 1263 s.disengage() 1264 return nil 1265} 1266 1267func (s *cScreen) Resume() error { 1268 return s.engage() 1269} 1270