| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329 |
- //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) 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 = 0x0008
- // modeWrapEOL = 0x0002
- )
- func (s *cScreen) setInMode(mode uint32) {
- _, _, _ = procSetConsoleMode.Call(
- uintptr(s.in),
- uintptr(mode))
- }
- func (s *cScreen) setOutMode(mode uint32) {
- _, _, _ = procSetConsoleMode.Call(
- uintptr(s.out),
- uintptr(mode))
- }
- func (s *cScreen) getInMode(v *uint32) {
- _, _, _ = procGetConsoleMode.Call(
- uintptr(s.in),
- uintptr(unsafe.Pointer(v)))
- }
- func (s *cScreen) getOutMode(v *uint32) {
- _, _, _ = procGetConsoleMode.Call(
- uintptr(s.out),
- uintptr(unsafe.Pointer(v)))
- }
- func (s *cScreen) SetStyle(style Style) {
- s.Lock()
- s.style = style
- s.Unlock()
- }
- // No fallback rune support, since we have Unicode. Yay!
- func (s *cScreen) RegisterRuneFallback(_ rune, _ string) {
- }
- func (s *cScreen) UnregisterRuneFallback(_ rune) {
- }
- func (s *cScreen) CanDisplay(_ rune, _ bool) bool {
- // We presume we can display anything -- we're Unicode.
- // (Sadly this not precisely true. Combining characters are especially
- // poorly supported under Windows.)
- return true
- }
- func (s *cScreen) HasMouse() bool {
- return true
- }
- func (s *cScreen) Resize(int, int, int, int) {}
- func (s *cScreen) HasKey(k Key) bool {
- // Microsoft has codes for some keys, but they are unusual,
- // so we don't include them. We include all the typical
- // 101, 105 key layout keys.
- valid := map[Key]bool{
- KeyBackspace: true,
- KeyTab: true,
- KeyEscape: true,
- KeyPause: true,
- KeyPrint: true,
- KeyPgUp: true,
- KeyPgDn: true,
- KeyEnter: true,
- KeyEnd: true,
- KeyHome: true,
- KeyLeft: true,
- KeyUp: true,
- KeyRight: true,
- KeyDown: true,
- KeyInsert: true,
- KeyDelete: true,
- KeyF1: true,
- KeyF2: true,
- KeyF3: true,
- KeyF4: true,
- KeyF5: true,
- KeyF6: true,
- KeyF7: true,
- KeyF8: true,
- KeyF9: true,
- KeyF10: true,
- KeyF11: true,
- KeyF12: true,
- KeyRune: true,
- }
- return valid[k]
- }
- func (s *cScreen) Beep() error {
- // A simple beep. If the sound card is not available, the sound is generated
- // using the speaker.
- //
- // Reference:
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebeep
- const simpleBeep = 0xffffffff
- if rv, _, err := procMessageBeep.Call(simpleBeep); rv == 0 {
- return err
- }
- return nil
- }
- func (s *cScreen) Suspend() error {
- s.disengage()
- return nil
- }
- func (s *cScreen) Resume() error {
- return s.engage()
- }
|