summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/stefanhaller/tcell/v2/console_win.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/stefanhaller/tcell/v2/console_win.go')
-rw-r--r--vendor/github.com/stefanhaller/tcell/v2/console_win.go1333
1 files changed, 1333 insertions, 0 deletions
diff --git a/vendor/github.com/stefanhaller/tcell/v2/console_win.go b/vendor/github.com/stefanhaller/tcell/v2/console_win.go
new file mode 100644
index 000000000..01fa1f7bf
--- /dev/null
+++ b/vendor/github.com/stefanhaller/tcell/v2/console_win.go
@@ -0,0 +1,1333 @@
+//go:build windows
+// +build windows
+
+// Copyright 2022 The TCell Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use file except in compliance with the License.
+// You may obtain a copy of the license at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcell
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "sync"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+)
+
+type cScreen struct {
+ in syscall.Handle
+ out syscall.Handle
+ cancelflag syscall.Handle
+ scandone chan struct{}
+ evch chan Event
+ quit chan struct{}
+ curx int
+ cury int
+ style Style
+ clear bool
+ fini bool
+ vten bool
+ truecolor bool
+ running bool
+
+ w int
+ h int
+
+ oscreen consoleInfo
+ ocursor cursorInfo
+ cursorStyle CursorStyle
+ oimode uint32
+ oomode uint32
+ cells CellBuffer
+
+ finiOnce sync.Once
+
+ mouseEnabled bool
+ wg sync.WaitGroup
+ stopQ chan struct{}
+
+ sync.Mutex
+}
+
+var winLock sync.Mutex
+
+var winPalette = []Color{
+ ColorBlack,
+ ColorMaroon,
+ ColorGreen,
+ ColorNavy,
+ ColorOlive,
+ ColorPurple,
+ ColorTeal,
+ ColorSilver,
+ ColorGray,
+ ColorRed,
+ ColorLime,
+ ColorBlue,
+ ColorYellow,
+ ColorFuchsia,
+ ColorAqua,
+ ColorWhite,
+}
+
+var winColors = map[Color]Color{
+ ColorBlack: ColorBlack,
+ ColorMaroon: ColorMaroon,
+ ColorGreen: ColorGreen,
+ ColorNavy: ColorNavy,
+ ColorOlive: ColorOlive,
+ ColorPurple: ColorPurple,
+ ColorTeal: ColorTeal,
+ ColorSilver: ColorSilver,
+ ColorGray: ColorGray,
+ ColorRed: ColorRed,
+ ColorLime: ColorLime,
+ ColorBlue: ColorBlue,
+ ColorYellow: ColorYellow,
+ ColorFuchsia: ColorFuchsia,
+ ColorAqua: ColorAqua,
+ ColorWhite: ColorWhite,
+}
+
+var (
+ k32 = syscall.NewLazyDLL("kernel32.dll")
+ u32 = syscall.NewLazyDLL("user32.dll")
+)
+
+// We have to bring in the kernel32 and user32 DLLs directly, so we can get
+// access to some system calls that the core Go API lacks.
+//
+// Note that Windows appends some functions with W to indicate that wide
+// characters (Unicode) are in use. The documentation refers to them
+// without this suffix, as the resolution is made via preprocessor.
+var (
+ procReadConsoleInput = k32.NewProc("ReadConsoleInputW")
+ procWaitForMultipleObjects = k32.NewProc("WaitForMultipleObjects")
+ procCreateEvent = k32.NewProc("CreateEventW")
+ procSetEvent = k32.NewProc("SetEvent")
+ procGetConsoleCursorInfo = k32.NewProc("GetConsoleCursorInfo")
+ procSetConsoleCursorInfo = k32.NewProc("SetConsoleCursorInfo")
+ procSetConsoleCursorPosition = k32.NewProc("SetConsoleCursorPosition")
+ procSetConsoleMode = k32.NewProc("SetConsoleMode")
+ procGetConsoleMode = k32.NewProc("GetConsoleMode")
+ procGetConsoleScreenBufferInfo = k32.NewProc("GetConsoleScreenBufferInfo")
+ procFillConsoleOutputAttribute = k32.NewProc("FillConsoleOutputAttribute")
+ procFillConsoleOutputCharacter = k32.NewProc("FillConsoleOutputCharacterW")
+ procSetConsoleWindowInfo = k32.NewProc("SetConsoleWindowInfo")
+ procSetConsoleScreenBufferSize = k32.NewProc("SetConsoleScreenBufferSize")
+ procSetConsoleTextAttribute = k32.NewProc("SetConsoleTextAttribute")
+ procGetLargestConsoleWindowSize = k32.NewProc("GetLargestConsoleWindowSize")
+ procMessageBeep = u32.NewProc("MessageBeep")
+)
+
+const (
+ w32Infinite = ^uintptr(0)
+ w32WaitObject0 = uintptr(0)
+)
+
+const (
+ // VT100/XTerm escapes understood by the console
+ vtShowCursor = "\x1b[?25h"
+ vtHideCursor = "\x1b[?25l"
+ vtCursorPos = "\x1b[%d;%dH" // Note that it is Y then X
+ vtSgr0 = "\x1b[0m"
+ vtBold = "\x1b[1m"
+ vtUnderline = "\x1b[4m"
+ vtBlink = "\x1b[5m" // Not sure this is processed
+ vtReverse = "\x1b[7m"
+ vtSetFg = "\x1b[38;5;%dm"
+ vtSetBg = "\x1b[48;5;%dm"
+ vtSetFgRGB = "\x1b[38;2;%d;%d;%dm" // RGB
+ vtSetBgRGB = "\x1b[48;2;%d;%d;%dm" // RGB
+ vtCursorDefault = "\x1b[0 q"
+ vtCursorBlinkingBlock = "\x1b[1 q"
+ vtCursorSteadyBlock = "\x1b[2 q"
+ vtCursorBlinkingUnderline = "\x1b[3 q"
+ vtCursorSteadyUnderline = "\x1b[4 q"
+ vtCursorBlinkingBar = "\x1b[5 q"
+ vtCursorSteadyBar = "\x1b[6 q"
+)
+
+var vtCursorStyles = map[CursorStyle]string{
+ CursorStyleDefault: vtCursorDefault,
+ CursorStyleBlinkingBlock: vtCursorBlinkingBlock,
+ CursorStyleSteadyBlock: vtCursorSteadyBlock,
+ CursorStyleBlinkingUnderline: vtCursorBlinkingUnderline,
+ CursorStyleSteadyUnderline: vtCursorSteadyUnderline,
+ CursorStyleBlinkingBar: vtCursorBlinkingBar,
+ CursorStyleSteadyBar: vtCursorSteadyBar,
+}
+
+// NewConsoleScreen returns a Screen for the Windows console associated
+// with the current process. The Screen makes use of the Windows Console
+// API to display content and read events.
+func NewConsoleScreen() (Screen, error) {
+ return &cScreen{}, nil
+}
+
+func (s *cScreen) Init() error {
+ s.evch = make(chan Event, 10)
+ s.quit = make(chan struct{})
+ s.scandone = make(chan struct{})
+
+ in, e := syscall.Open("CONIN$", syscall.O_RDWR, 0)
+ if e != nil {
+ return e
+ }
+ s.in = in
+ out, e := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
+ if e != nil {
+ _ = syscall.Close(s.in)
+ return e
+ }
+ s.out = out
+
+ s.truecolor = true
+
+ // ConEmu handling of colors and scrolling when in terminal
+ // mode is extremely problematic at the best. The color
+ // palette will scroll even though characters do not, when
+ // emitting stuff for the last character. In the future we
+ // might change this to look at specific versions of ConEmu
+ // if they fix the bug.
+ if os.Getenv("ConEmuPID") != "" {
+ s.truecolor = false
+ }
+ switch os.Getenv("TCELL_TRUECOLOR") {
+ case "disable":
+ s.truecolor = false
+ case "enable":
+ s.truecolor = true
+ }
+
+ s.Lock()
+
+ s.curx = -1
+ s.cury = -1
+ s.style = StyleDefault
+ s.getCursorInfo(&s.ocursor)
+ s.getConsoleInfo(&s.oscreen)
+ s.getOutMode(&s.oomode)
+ s.getInMode(&s.oimode)
+ s.resize()
+
+ s.fini = false
+ s.setInMode(modeResizeEn | modeExtendFlg)
+
+ // 24-bit color is opt-in for now, because we can't figure out
+ // to make it work consistently.
+ if s.truecolor {
+ s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
+ var om uint32
+ s.getOutMode(&om)
+ if om&modeVtOutput == modeVtOutput {
+ s.vten = true
+ } else {
+ s.truecolor = false
+ s.setOutMode(0)
+ }
+ } else {
+ s.setOutMode(0)
+ }
+
+ s.Unlock()
+
+ return s.engage()
+}
+
+func (s *cScreen) CharacterSet() string {
+ // We are always UTF-16LE on Windows
+ return "UTF-16LE"
+}
+
+func (s *cScreen) EnableMouse(...MouseFlags) {
+ s.Lock()
+ s.mouseEnabled = true
+ s.enableMouse(true)
+ s.Unlock()
+}
+
+func (s *cScreen) DisableMouse() {
+ s.Lock()
+ s.mouseEnabled = false
+ s.enableMouse(false)
+ s.Unlock()
+}
+
+func (s *cScreen) enableMouse(on bool) {
+ if on {
+ s.setInMode(modeResizeEn | modeMouseEn | modeExtendFlg)
+ } else {
+ s.setInMode(modeResizeEn | modeExtendFlg)
+ }
+}
+
+// Windows lacks bracketed paste (for now)
+
+func (s *cScreen) EnablePaste() {}
+
+func (s *cScreen) DisablePaste() {}
+
+func (s *cScreen) EnableFocus() {}
+
+func (s *cScreen) DisableFocus() {}
+
+func (s *cScreen) Fini() {
+ s.disengage()
+}
+
+func (s *cScreen) disengage() {
+ s.Lock()
+ if !s.running {
+ s.Unlock()
+ return
+ }
+ s.running = false
+ stopQ := s.stopQ
+ _, _, _ = procSetEvent.Call(uintptr(s.cancelflag))
+ close(stopQ)
+ s.Unlock()
+
+ s.wg.Wait()
+
+ if s.vten {
+ s.emitVtString(vtCursorStyles[CursorStyleDefault])
+ }
+ s.setInMode(s.oimode)
+ s.setOutMode(s.oomode)
+ s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y))
+ s.clearScreen(StyleDefault, false)
+ s.setCursorPos(0, 0, false)
+ s.setCursorInfo(&s.ocursor)
+ _, _, _ = procSetConsoleTextAttribute.Call(
+ uintptr(s.out),
+ uintptr(s.mapStyle(StyleDefault)))
+}
+
+func (s *cScreen) engage() error {
+ s.Lock()
+ defer s.Unlock()
+ if s.running {
+ return errors.New("already engaged")
+ }
+ s.stopQ = make(chan struct{})
+ cf, _, e := procCreateEvent.Call(
+ uintptr(0),
+ uintptr(1),
+ uintptr(0),
+ uintptr(0))
+ if cf == uintptr(0) {
+ return e
+ }
+ s.running = true
+ s.cancelflag = syscall.Handle(cf)
+ s.enableMouse(s.mouseEnabled)
+
+ if s.vten {
+ s.setOutMode(modeVtOutput | modeNoAutoNL | modeCookedOut)
+ } else {
+ s.setOutMode(0)
+ }
+
+ s.clearScreen(s.style, s.vten)
+ s.hideCursor()
+
+ s.cells.Invalidate()
+ s.hideCursor()
+ s.resize()
+ s.draw()
+ s.doCursor()
+
+ s.wg.Add(1)
+ go s.scanInput(s.stopQ)
+ return nil
+}
+
+func (s *cScreen) PostEventWait(ev Event) {
+ s.evch <- ev
+}
+
+func (s *cScreen) PostEvent(ev Event) error {
+ select {
+ case s.evch <- ev:
+ return nil
+ default:
+ return ErrEventQFull
+ }
+}
+
+func (s *cScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
+ defer close(ch)
+ for {
+ select {
+ case <-quit:
+ return
+ case <-s.stopQ:
+ return
+ case ev := <-s.evch:
+ select {
+ case <-quit:
+ return
+ case <-s.stopQ:
+ return
+ case ch <- ev:
+ }
+ }
+ }
+}
+
+func (s *cScreen) PollEvent() Event {
+ select {
+ case <-s.stopQ:
+ return nil
+ case ev := <-s.evch:
+ return ev
+ }
+}
+
+func (s *cScreen) HasPendingEvent() bool {
+ return len(s.evch) > 0
+}
+
+type cursorInfo struct {
+ size uint32
+ visible uint32
+}
+
+type coord struct {
+ x int16
+ y int16
+}
+
+func (c coord) uintptr() uintptr {
+ // little endian, put x first
+ return uintptr(c.x) | (uintptr(c.y) << 16)
+}
+
+type rect struct {
+ left int16
+ top int16
+ right int16
+ bottom int16
+}
+
+func (s *cScreen) emitVtString(vs string) {
+ esc := utf16.Encode([]rune(vs))
+ _ = syscall.WriteConsole(s.out, &esc[0], uint32(len(esc)), nil, nil)
+}
+
+func (s *cScreen) showCursor() {
+ if s.vten {
+ s.emitVtString(vtShowCursor)
+ s.emitVtString(vtCursorStyles[s.cursorStyle])
+ } else {
+ s.setCursorInfo(&cursorInfo{size: 100, visible: 1})
+ }
+}
+
+func (s *cScreen) hideCursor() {
+ if s.vten {
+ s.emitVtString(vtHideCursor)
+ } else {
+ s.setCursorInfo(&cursorInfo{size: 1, visible: 0})
+ }
+}
+
+func (s *cScreen) ShowCursor(x, y int) {
+ s.Lock()
+ if !s.fini {
+ s.curx = x
+ s.cury = y
+ }
+ s.doCursor()
+ s.Unlock()
+}
+
+func (s *cScreen) SetCursorStyle(cs CursorStyle) {
+ s.Lock()
+ if !s.fini {
+ if _, ok := vtCursorStyles[cs]; ok {
+ s.cursorStyle = cs
+ s.doCursor()
+ }
+ }
+ s.Unlock()
+}
+
+func (s *cScreen) doCursor() {
+ x, y := s.curx, s.cury
+
+ if x < 0 || y < 0 || x >= s.w || y >= s.h {
+ s.hideCursor()
+ } else {
+ s.setCursorPos(x, y, s.vten)
+ s.showCursor()
+ }
+}
+
+func (s *cScreen) HideCursor() {
+ s.ShowCursor(-1, -1)
+}
+
+type inputRecord struct {
+ typ uint16
+ _ uint16
+ data [16]byte
+}
+
+const (
+ keyEvent uint16 = 1
+ mouseEvent uint16 = 2
+ resizeEvent uint16 = 4
+ // menuEvent uint16 = 8 // don't use
+ // focusEvent uint16 = 16 // don't use
+)
+
+type mouseRecord struct {
+ x int16
+ y int16
+ btns uint32
+ mod uint32
+ flags uint32
+}
+
+const (
+ mouseHWheeled uint32 = 0x8
+ mouseVWheeled uint32 = 0x4
+ // mouseDoubleClick uint32 = 0x2
+ // mouseMoved uint32 = 0x1
+)
+
+type resizeRecord struct {
+ x int16
+ y int16
+}
+
+type keyRecord struct {
+ isdown int32
+ repeat uint16
+ kcode uint16
+ scode uint16
+ ch uint16
+ mod uint32
+}
+
+const (
+ // Constants per Microsoft. We don't put the modifiers
+ // here.
+ vkCancel = 0x03
+ vkBack = 0x08 // Backspace
+ vkTab = 0x09
+ vkClear = 0x0c
+ vkReturn = 0x0d
+ vkPause = 0x13
+ vkEscape = 0x1b
+ vkSpace = 0x20
+ vkPrior = 0x21 // PgUp
+ vkNext = 0x22 // PgDn
+ vkEnd = 0x23
+ vkHome = 0x24
+ vkLeft = 0x25
+ vkUp = 0x26
+ vkRight = 0x27
+ vkDown = 0x28
+ vkPrint = 0x2a
+ vkPrtScr = 0x2c
+ vkInsert = 0x2d
+ vkDelete = 0x2e
+ vkHelp = 0x2f
+ vkF1 = 0x70
+ vkF2 = 0x71
+ vkF3 = 0x72
+ vkF4 = 0x73
+ vkF5 = 0x74
+ vkF6 = 0x75
+ vkF7 = 0x76
+ vkF8 = 0x77
+ vkF9 = 0x78
+ vkF10 = 0x79
+ vkF11 = 0x7a
+ vkF12 = 0x7b
+ vkF13 = 0x7c
+ vkF14 = 0x7d
+ vkF15 = 0x7e
+ vkF16 = 0x7f
+ vkF17 = 0x80
+ vkF18 = 0x81
+ vkF19 = 0x82
+ vkF20 = 0x83
+ vkF21 = 0x84
+ vkF22 = 0x85
+ vkF23 = 0x86
+ vkF24 = 0x87
+)
+
+var vkKeys = map[uint16]Key{
+ vkCancel: KeyCancel,
+ vkBack: KeyBackspace,
+ vkTab: KeyTab,
+ vkClear: KeyClear,
+ vkPause: KeyPause,
+ vkPrint: KeyPrint,
+ vkPrtScr: KeyPrint,
+ vkPrior: KeyPgUp,
+ vkNext: KeyPgDn,
+ vkReturn: KeyEnter,
+ vkEnd: KeyEnd,
+ vkHome: KeyHome,
+ vkLeft: KeyLeft,
+ vkUp: KeyUp,
+ vkRight: KeyRight,
+ vkDown: KeyDown,
+ vkInsert: KeyInsert,
+ vkDelete: KeyDelete,
+ vkHelp: KeyHelp,
+ vkEscape: KeyEscape,
+ vkSpace: ' ',
+ vkF1: KeyF1,
+ vkF2: KeyF2,
+ vkF3: KeyF3,
+ vkF4: KeyF4,
+ vkF5: KeyF5,
+ vkF6: KeyF6,
+ vkF7: KeyF7,
+ vkF8: KeyF8,
+ vkF9: KeyF9,
+ vkF10: KeyF10,
+ vkF11: KeyF11,
+ vkF12: KeyF12,
+ vkF13: KeyF13,
+ vkF14: KeyF14,
+ vkF15: KeyF15,
+ vkF16: KeyF16,
+ vkF17: KeyF17,
+ vkF18: KeyF18,
+ vkF19: KeyF19,
+ vkF20: KeyF20,
+ vkF21: KeyF21,
+ vkF22: KeyF22,
+ vkF23: KeyF23,
+ vkF24: KeyF24,
+}
+
+// NB: All Windows platforms are little endian. We assume this
+// never, ever change. The following code is endian safe. and does
+// not use unsafe pointers.
+func getu32(v []byte) uint32 {
+ return uint32(v[0]) + (uint32(v[1]) << 8) + (uint32(v[2]) << 16) + (uint32(v[3]) << 24)
+}
+func geti32(v []byte) int32 {
+ return int32(getu32(v))
+}
+func getu16(v []byte) uint16 {
+ return uint16(v[0]) + (uint16(v[1]) << 8)
+}
+func geti16(v []byte) int16 {
+ return int16(getu16(v))
+}
+
+// Convert windows dwControlKeyState to modifier mask
+func mod2mask(cks uint32) ModMask {
+ mm := ModNone
+ // Left or right control
+ if (cks & (0x0008 | 0x0004)) != 0 {
+ mm |= ModCtrl
+ }
+ // Left or right alt
+ if (cks & (0x0002 | 0x0001)) != 0 {
+ mm |= ModAlt
+ }
+ // Any shift
+ if (cks & 0x0010) != 0 {
+ mm |= ModShift
+ }
+ return mm
+}
+
+func mrec2btns(mbtns, flags uint32) ButtonMask {
+ btns := ButtonNone
+ if mbtns&0x1 != 0 {
+ btns |= Button1
+ }
+ if mbtns&0x2 != 0 {
+ btns |= Button2
+ }
+ if mbtns&0x4 != 0 {
+ btns |= Button3
+ }
+ if mbtns&0x8 != 0 {
+ btns |= Button4
+ }
+ if mbtns&0x10 != 0 {
+ btns |= Button5
+ }
+ if mbtns&0x20 != 0 {
+ btns |= Button6
+ }
+ if mbtns&0x40 != 0 {
+ btns |= Button7
+ }
+ if mbtns&0x80 != 0 {
+ btns |= Button8
+ }
+
+ if flags&mouseVWheeled != 0 {
+ if mbtns&0x80000000 == 0 {
+ btns |= WheelUp
+ } else {
+ btns |= WheelDown
+ }
+ }
+ if flags&mouseHWheeled != 0 {
+ if mbtns&0x80000000 == 0 {
+ btns |= WheelRight
+ } else {
+ btns |= WheelLeft
+ }
+ }
+ return btns
+}
+
+func (s *cScreen) getConsoleInput() error {
+ // cancelFlag comes first as WaitForMultipleObjects returns the lowest index
+ // in the event that both events are signalled.
+ waitObjects := []syscall.Handle{s.cancelflag, s.in}
+ // As arrays are contiguous in memory, a pointer to the first object is the
+ // same as a pointer to the array itself.
+ pWaitObjects := unsafe.Pointer(&waitObjects[0])
+
+ rv, _, er := procWaitForMultipleObjects.Call(
+ uintptr(len(waitObjects)),
+ uintptr(pWaitObjects),
+ uintptr(0),
+ w32Infinite)
+ // WaitForMultipleObjects returns WAIT_OBJECT_0 + the index.
+ switch rv {
+ case w32WaitObject0: // s.cancelFlag
+ return errors.New("cancelled")
+ case w32WaitObject0 + 1: // s.in
+ rec := &inputRecord{}
+ var nrec int32
+ rv, _, er := procReadConsoleInput.Call(
+ uintptr(s.in),
+ uintptr(unsafe.Pointer(rec)),
+ uintptr(1),
+ uintptr(unsafe.Pointer(&nrec)))
+ if rv == 0 {
+ return er
+ }
+ if nrec != 1 {
+ return nil
+ }
+ switch rec.typ {
+ case keyEvent:
+ krec := &keyRecord{}
+ krec.isdown = geti32(rec.data[0:])
+ krec.repeat = getu16(rec.data[4:])
+ krec.kcode = getu16(rec.data[6:])
+ krec.scode = getu16(rec.data[8:])
+ krec.ch = getu16(rec.data[10:])
+ krec.mod = getu32(rec.data[12:])
+
+ if krec.isdown == 0 || krec.repeat < 1 {
+ // its a key release event, ignore it
+ return nil
+ }
+ if krec.ch != 0 {
+ // synthesized key code
+ for krec.repeat > 0 {
+ // convert shift+tab to backtab
+ if mod2mask(krec.mod) == ModShift && krec.ch == vkTab {
+ s.PostEventWait(NewEventKey(KeyBacktab, 0,
+ ModNone))
+ } else {
+ s.PostEventWait(NewEventKey(KeyRune, rune(krec.ch),
+ mod2mask(krec.mod)))
+ }
+ krec.repeat--
+ }
+ return nil
+ }
+ key := KeyNUL // impossible on Windows
+ ok := false
+ if key, ok = vkKeys[krec.kcode]; !ok {
+ return nil
+ }
+ for krec.repeat > 0 {
+ s.PostEventWait(NewEventKey(key, rune(krec.ch),
+ mod2mask(krec.mod)))
+ krec.repeat--
+ }
+
+ case mouseEvent:
+ var mrec mouseRecord
+ mrec.x = geti16(rec.data[0:])
+ mrec.y = geti16(rec.data[2:])
+ mrec.btns = getu32(rec.data[4:])
+ mrec.mod = getu32(rec.data[8:])
+ mrec.flags = getu32(rec.data[12:])
+ btns := mrec2btns(mrec.btns, mrec.flags)
+ // we ignore double click, events are delivered normally
+ s.PostEventWait(NewEventMouse(int(mrec.x), int(mrec.y), btns,
+ mod2mask(mrec.mod)))
+
+ case resizeEvent:
+ var rrec resizeRecord
+ rrec.x = geti16(rec.data[0:])
+ rrec.y = geti16(rec.data[2:])
+ s.PostEventWait(NewEventResize(int(rrec.x), int(rrec.y)))
+
+ default:
+ }
+ default:
+ return er
+ }
+
+ return nil
+}
+
+func (s *cScreen) scanInput(stopQ chan struct{}) {
+ defer s.wg.Done()
+ for {
+ select {
+ case <-stopQ:
+ return
+ default:
+ }
+ if e := s.getConsoleInput(); e != nil {
+ return
+ }
+ }
+}
+
+func (s *cScreen) Colors() int {
+ if s.vten {
+ return 1 << 24
+ }
+ // Windows console can display 8 colors, in either low or high intensity
+ return 16
+}
+
+var vgaColors = map[Color]uint16{
+ ColorBlack: 0,
+ ColorMaroon: 0x4,
+ ColorGreen: 0x2,
+ ColorNavy: 0x1,
+ ColorOlive: 0x6,
+ ColorPurple: 0x5,
+ ColorTeal: 0x3,
+ ColorSilver: 0x7,
+ ColorGrey: 0x8,
+ ColorRed: 0xc,
+ ColorLime: 0xa,
+ ColorBlue: 0x9,
+ ColorYellow: 0xe,
+ ColorFuchsia: 0xd,
+ ColorAqua: 0xb,
+ ColorWhite: 0xf,
+}
+
+// Windows uses RGB signals
+func mapColor2RGB(c Color) uint16 {
+ winLock.Lock()
+ if v, ok := winColors[c]; ok {
+ c = v
+ } else {
+ v = FindColor(c, winPalette)
+ winColors[c] = v
+ c = v
+ }
+ winLock.Unlock()
+
+ if vc, ok := vgaColors[c]; ok {
+ return vc
+ }
+ return 0
+}
+
+// Map a tcell style to Windows attributes
+func (s *cScreen) mapStyle(style Style) uint16 {
+ f, b, a := style.Decompose()
+ fa := s.oscreen.attrs & 0xf
+ ba := (s.oscreen.attrs) >> 4 & 0xf
+ if f != ColorDefault && f != ColorReset {
+ fa = mapColor2RGB(f)
+ }
+ if b != ColorDefault && b != ColorReset {
+ ba = mapColor2RGB(b)
+ }
+ var attr uint16
+ // We simulate reverse by doing the color swap ourselves.
+ // Apparently windows cannot really do this except in DBCS
+ // views.
+ if a&AttrReverse != 0 {
+ attr = ba
+ attr |= fa << 4
+ } else {
+ attr = fa
+ attr |= ba << 4
+ }
+ if a&AttrBold != 0 {
+ attr |= 0x8
+ }
+ if a&AttrDim != 0 {
+ attr &^= 0x8
+ }
+ if a&AttrUnderline != 0 {
+ // Best effort -- doesn't seem to work though.
+ attr |= 0x8000
+ }
+ // Blink is unsupported
+ return attr
+}
+
+func (s *cScreen) SetCell(x, y int, style Style, ch ...rune) {
+ if len(ch) > 0 {
+ s.SetContent(x, y, ch[0], ch[1:], style)
+ } else {
+ s.SetContent(x, y, ' ', nil, style)
+ }
+}
+
+func (s *cScreen) SetContent(x, y int, primary rune, combining []rune, style Style) {
+ s.Lock()
+ if !s.fini {
+ s.cells.SetContent(x, y, primary, combining, style)
+ }
+ s.Unlock()
+}
+
+func (s *cScreen) GetContent(x, y int) (rune, []rune, Style, int) {
+ s.Lock()
+ primary, combining, style, width := s.cells.GetContent(x, y)
+ s.Unlock()
+ return primary, combining, style, width
+}
+
+func (s *cScreen) sendVtStyle(style Style) {
+ esc := &strings.Builder{}
+
+ fg, bg, attrs := style.Decompose()
+
+ esc.WriteString(vtSgr0)
+
+ if attrs&(AttrBold|AttrDim) == AttrBold {
+ esc.WriteString(vtBold)
+ }
+ if attrs&AttrBlink != 0 {
+ esc.WriteString(vtBlink)
+ }
+ if attrs&AttrUnderline != 0 {
+ esc.WriteString(vtUnderline)
+ }
+ if attrs&AttrReverse != 0 {
+ esc.WriteString(vtReverse)
+ }
+ if fg.IsRGB() {
+ r, g, b := fg.RGB()
+ _, _ = fmt.Fprintf(esc, vtSetFgRGB, r, g, b)
+ } else if fg.Valid() {
+ _, _ = fmt.Fprintf(esc, vtSetFg, fg&0xff)
+ }
+ if bg.IsRGB() {
+ r, g, b := bg.RGB()
+ _, _ = fmt.Fprintf(esc, vtSetBgRGB, r, g, b)
+ } else if bg.Valid() {
+ _, _ = fmt.Fprintf(esc, vtSetBg, bg&0xff)
+ }
+ s.emitVtString(esc.String())
+}
+
+func (s *cScreen) writeString(x, y int, style Style, ch []uint16) {
+ // we assume the caller has hidden the cursor
+ if len(ch) == 0 {
+ return
+ }
+ s.setCursorPos(x, y, s.vten)
+
+ if s.vten {
+ s.sendVtStyle(style)
+ } else {
+ _, _, _ = procSetConsoleTextAttribute.Call(
+ uintptr(s.out),
+ uintptr(s.mapStyle(style)))
+ }
+ _ = syscall.WriteConsole(s.out, &ch[0], uint32(len(ch)), nil, nil)
+}
+
+func (s *cScreen) draw() {
+ // allocate a scratch line bit enough for no combining chars.
+ // if you have combining characters, you may pay for extra allocations.
+ if s.clear {
+ s.clearScreen(s.style, s.vten)
+ s.clear = false
+ s.cells.Invalidate()
+ }
+ buf := make([]uint16, 0, s.w)
+ wcs := buf[:]
+ lstyle := styleInvalid
+
+ lx, ly := -1, -1
+ ra := make([]rune, 1)
+
+ for y := 0; y < s.h; y++ {
+ for x := 0; x < s.w; x++ {
+ mainc, combc, style, width := s.cells.GetContent(x, y)
+ dirty := s.cells.Dirty(x, y)
+ if style == StyleDefault {
+ style = s.style
+ }
+
+ if !dirty || style != lstyle {
+ // write out any data queued thus far
+ // because we are going to skip over some
+ // cells, or because we need to change styles
+ s.writeString(lx, ly, lstyle, wcs)
+ wcs = buf[0:0]
+ lstyle = StyleDefault
+ if !dirty {
+ continue
+ }
+ }
+ if x > s.w-width {
+ mainc = ' '
+ combc = nil
+ width = 1
+ }
+ if len(wcs) == 0 {
+ lstyle = style
+ lx = x
+ ly = y
+ }
+ ra[0] = mainc
+ wcs = append(wcs, utf16.Encode(ra)...)
+ if len(combc) != 0 {
+ wcs = append(wcs, utf16.Encode(combc)...)
+ }
+ for dx := 0; dx < width; dx++ {
+ s.cells.SetDirty(x+dx, y, false)
+ }
+ x += width - 1
+ }
+ s.writeString(lx, ly, lstyle, wcs)
+ wcs = buf[0:0]
+ lstyle = styleInvalid
+ }
+}
+
+func (s *cScreen) Show() {
+ s.Lock()
+ if !s.fini {
+ s.hideCursor()
+ s.resize()
+ s.draw()
+ s.doCursor()
+ }
+ s.Unlock()
+}
+
+func (s *cScreen) Sync() {
+ s.Lock()
+ if !s.fini {
+ s.cells.Invalidate()
+ s.hideCursor()
+ s.resize()
+ s.draw()
+ s.doCursor()
+ }
+ s.Unlock()
+}
+
+type consoleInfo struct {
+ size coord
+ pos coord
+ attrs uint16
+ win rect
+ maxsz coord
+}
+
+func (s *cScreen) getConsoleInfo(info *consoleInfo) {
+ _, _, _ = procGetConsoleScreenBufferInfo.Call(
+ uintptr(s.out),
+ uintptr(unsafe.Pointer(info)))
+}
+
+func (s *cScreen) getCursorInfo(info *cursorInfo) {
+ _, _, _ = procGetConsoleCursorInfo.Call(
+ uintptr(s.out),
+ uintptr(unsafe.Pointer(info)))
+}
+
+func (s *cScreen) setCursorInfo(info *cursorInfo) {
+ _, _, _ = procSetConsoleCursorInfo.Call(
+ uintptr(s.out),
+ uintptr(unsafe.Pointer(info)))
+
+}
+
+func (s *cScreen) setCursorPos(x, y int, vtEnable bool) {
+ if vtEnable {
+ // Note that the string is Y first. Origin is 1,1.
+ s.emitVtString(fmt.Sprintf(vtCursorPos, y+1, x+1))
+ } else {
+ _, _, _ = procSetConsoleCursorPosition.Call(
+ uintptr(s.out),
+ coord{int16(x), int16(y)}.uintptr())
+ }
+}
+
+func (s *cScreen) setBufferSize(x, y int) {
+ _, _, _ = procSetConsoleScreenBufferSize.Call(
+ uintptr(s.out),
+ coord{int16(x), int16(y)}.uintptr())
+}
+
+func (s *cScreen) Size() (int, int) {
+ s.Lock()
+ w, h := s.w, s.h
+ s.Unlock()
+
+ return w, h
+}
+
+func (s *cScreen) SetSize(w, h int) {
+ xy, _, _ := procGetLargestConsoleWindowSize.Call(uintptr(s.out))
+
+ // xy is little endian packed
+ y := int(xy >> 16)
+ x := int(xy & 0xffff)
+
+ if x == 0 || y == 0 {
+ return
+ }
+
+ // This is a hacky workaround for Windows Terminal.
+ // Essentially Windows Terminal (Windows 11) does not support application
+ // initiated resizing. To detect this, we look for an extremely large size
+ // for the maximum width. If it is > 500, then this is almost certainly
+ // Windows Terminal, and won't support this. (Note that the legacy console
+ // does support application resizing.)
+ if x >= 500 {
+ return
+ }
+
+ s.setBufferSize(x, y)
+ r := rect{0, 0, int16(w - 1), int16(h - 1)}
+ _, _, _ = procSetConsoleWindowInfo.Call(
+ uintptr(s.out),
+ uintptr(1),
+ uintptr(unsafe.Pointer(&r)))
+
+ s.resize()
+}
+
+func (s *cScreen) resize() {
+ info := consoleInfo{}
+ s.getConsoleInfo(&info)
+
+ w := int((info.win.right - info.win.left) + 1)
+ h := int((info.win.bottom - info.win.top) + 1)
+
+ if s.w == w && s.h == h {
+ return
+ }
+
+ s.cells.Resize(w, h)
+ s.w = w
+ s.h = h
+
+ s.setBufferSize(w, h)
+
+ r := rect{0, 0, int16(w - 1), int16(h - 1)}
+ _, _, _ = procSetConsoleWindowInfo.Call(
+ uintptr(s.out),
+ uintptr(1),
+ uintptr(unsafe.Pointer(&r)))
+ _ = s.PostEvent(NewEventResize(w, h))
+}
+
+func (s *cScreen) Clear() {
+ s.Fill(' ', s.style)
+}
+
+func (s *cScreen) Fill(r rune, style Style) {
+ s.Lock()
+ if !s.fini {
+ s.cells.Fill(r, style)
+ s.clear = true
+ }
+ s.Unlock()
+}
+
+func (s *cScreen) clearScreen(style Style, vtEnable bool) {
+ if vtEnable {
+ s.sendVtStyle(style)
+ row := strings.Repeat(" ", s.w)
+ for y := 0; y < s.h; y++ {
+ s.setCursorPos(0, y, vtEnable)
+ s.emitVtString(row)
+ }
+ s.setCursorPos(0, 0, vtEnable)
+
+ } else {
+ pos := coord{0, 0}
+ attr := s.mapStyle(style)
+ x, y := s.w, s.h
+ scratch := uint32(0)
+ count := uint32(x * y)
+
+ _, _, _ = procFillConsoleOutputAttribute.Call(
+ uintptr(s.out),
+ uintptr(attr),
+ uintptr(count),
+ pos.uintptr(),
+ uintptr(unsafe.Pointer(&scratch)))
+ _, _, _ = procFillConsoleOutputCharacter.Call(
+ uintptr(s.out),
+ uintptr(' '),
+ uintptr(count),
+ pos.uintptr(),
+ uintptr(unsafe.Pointer(&scratch)))
+ }
+}
+
+const (
+ // Input modes
+ modeExtendFlg uint32 = 0x0080
+ modeMouseEn = 0x0010
+ modeResizeEn = 0x0008
+ // modeCooked = 0x0001
+ // modeVtInput = 0x0200
+
+ // Output modes
+ modeCookedOut uint32 = 0x0001
+ modeVtOutput = 0x0004
+ modeNoAutoNL