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