| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871 |
- // 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.
- //go:build !(js && wasm)
- // +build !js !wasm
- package tcell
- import (
- "bytes"
- "errors"
- "io"
- "os"
- "strconv"
- "strings"
- "sync"
- "time"
- "unicode/utf8"
- "golang.org/x/term"
- "golang.org/x/text/transform"
- "github.com/gdamore/tcell/v2/terminfo"
- // import the stock terminals
- _ "github.com/gdamore/tcell/v2/terminfo/base"
- )
- // NewTerminfoScreen returns a Screen that uses the stock TTY interface
- // and POSIX terminal control, combined with a terminfo description taken from
- // the $TERM environment variable. It returns an error if the terminal
- // is not supported for any reason.
- //
- // For terminals that do not support dynamic resize events, the $LINES
- // $COLUMNS environment variables can be set to the actual window size,
- // otherwise defaults taken from the terminal database are used.
- func NewTerminfoScreen() (Screen, error) {
- return NewTerminfoScreenFromTty(nil)
- }
- // LookupTerminfo attempts to find a definition for the named $TERM falling
- // back to attempting to parse the output from infocmp.
- func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) {
- ti, e = terminfo.LookupTerminfo(name)
- if e != nil {
- ti, e = loadDynamicTerminfo(name)
- if e != nil {
- return nil, e
- }
- terminfo.AddTerminfo(ti)
- }
- return
- }
- // NewTerminfoScreenFromTtyTerminfo returns a Screen using a custom Tty
- // implementation and custom terminfo specification.
- // If the passed in tty is nil, then a reasonable default (typically /dev/tty)
- // is presumed, at least on UNIX hosts. (Windows hosts will typically fail this
- // call altogether.)
- // If passed terminfo is nil, then TERM environment variable is queried for
- // terminal specification.
- func NewTerminfoScreenFromTtyTerminfo(tty Tty, ti *terminfo.Terminfo) (s Screen, e error) {
- if ti == nil {
- ti, e = LookupTerminfo(os.Getenv("TERM"))
- if e != nil {
- return
- }
- }
- t := &tScreen{ti: ti, tty: tty}
- t.keyexist = make(map[Key]bool)
- t.keycodes = make(map[string]*tKeyCode)
- if len(ti.Mouse) > 0 {
- t.mouse = []byte(ti.Mouse)
- }
- t.prepareKeys()
- t.buildAcsMap()
- t.resizeQ = make(chan bool, 1)
- t.fallback = make(map[rune]string)
- for k, v := range RuneFallbacks {
- t.fallback[k] = v
- }
- return t, nil
- }
- // NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation.
- // If the passed in tty is nil, then a reasonable default (typically /dev/tty)
- // is presumed, at least on UNIX hosts. (Windows hosts will typically fail this
- // call altogether.)
- func NewTerminfoScreenFromTty(tty Tty) (Screen, error) {
- return NewTerminfoScreenFromTtyTerminfo(tty, nil)
- }
- // tKeyCode represents a combination of a key code and modifiers.
- type tKeyCode struct {
- key Key
- mod ModMask
- }
- // tScreen represents a screen backed by a terminfo implementation.
- type tScreen struct {
- ti *terminfo.Terminfo
- tty Tty
- h int
- w int
- fini bool
- cells CellBuffer
- buffering bool // true if we are collecting writes to buf instead of sending directly to out
- buf bytes.Buffer
- curstyle Style
- style Style
- evch chan Event
- resizeQ chan bool
- quit chan struct{}
- keyexist map[Key]bool
- keycodes map[string]*tKeyCode
- keychan chan []byte
- keytimer *time.Timer
- keyexpire time.Time
- cx int
- cy int
- mouse []byte
- clear bool
- cursorx int
- cursory int
- acs map[rune]string
- charset string
- encoder transform.Transformer
- decoder transform.Transformer
- fallback map[rune]string
- colors map[Color]Color
- palette []Color
- truecolor bool
- escaped bool
- buttondn bool
- finiOnce sync.Once
- enablePaste string
- disablePaste string
- enterUrl string
- exitUrl string
- setWinSize string
- cursorStyles map[CursorStyle]string
- cursorStyle CursorStyle
- saved *term.State
- stopQ chan struct{}
- running bool
- wg sync.WaitGroup
- mouseFlags MouseFlags
- pasteEnabled bool
- sync.Mutex
- }
- func (t *tScreen) Init() error {
- if e := t.initialize(); e != nil {
- return e
- }
- t.evch = make(chan Event, 10)
- t.keychan = make(chan []byte, 10)
- t.keytimer = time.NewTimer(time.Millisecond * 50)
- t.charset = "UTF-8"
- t.charset = getCharset()
- if enc := GetEncoding(t.charset); enc != nil {
- t.encoder = enc.NewEncoder()
- t.decoder = enc.NewDecoder()
- } else {
- return ErrNoCharset
- }
- ti := t.ti
- // environment overrides
- w := ti.Columns
- h := ti.Lines
- if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 {
- h = i
- }
- if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 {
- w = i
- }
- if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" {
- t.truecolor = true
- }
- // A user who wants to have his themes honored can
- // set this environment variable.
- if os.Getenv("TCELL_TRUECOLOR") == "disable" {
- t.truecolor = false
- }
- nColors := t.nColors()
- if nColors > 256 {
- nColors = 256 // clip to reasonable limits
- }
- t.colors = make(map[Color]Color, nColors)
- t.palette = make([]Color, nColors)
- for i := 0; i < nColors; i++ {
- t.palette[i] = Color(i) | ColorValid
- // identity map for our builtin colors
- t.colors[Color(i)|ColorValid] = Color(i) | ColorValid
- }
- t.quit = make(chan struct{})
- t.Lock()
- t.cx = -1
- t.cy = -1
- t.style = StyleDefault
- t.cells.Resize(w, h)
- t.cursorx = -1
- t.cursory = -1
- t.resize()
- t.Unlock()
- if err := t.engage(); err != nil {
- return err
- }
- return nil
- }
- func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) {
- if val != "" {
- // Do not override codes that already exist
- if _, exist := t.keycodes[val]; !exist {
- t.keyexist[key] = true
- t.keycodes[val] = &tKeyCode{key: key, mod: mod}
- }
- }
- }
- func (t *tScreen) prepareKeyModReplace(key Key, replace Key, mod ModMask, val string) {
- if val != "" {
- // Do not override codes that already exist
- if old, exist := t.keycodes[val]; !exist || old.key == replace {
- t.keyexist[key] = true
- t.keycodes[val] = &tKeyCode{key: key, mod: mod}
- }
- }
- }
- func (t *tScreen) prepareKeyModXTerm(key Key, val string) {
- if strings.HasPrefix(val, "\x1b[") && strings.HasSuffix(val, "~") {
- // Drop the trailing ~
- val = val[:len(val)-1]
- // These suffixes are calculated assuming Xterm style modifier suffixes.
- // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for
- // more information (specifically "PC-Style Function Keys").
- t.prepareKeyModReplace(key, key+12, ModShift, val+";2~")
- t.prepareKeyModReplace(key, key+48, ModAlt, val+";3~")
- t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, val+";4~")
- t.prepareKeyModReplace(key, key+24, ModCtrl, val+";5~")
- t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, val+";6~")
- t.prepareKeyMod(key, ModAlt|ModCtrl, val+";7~")
- t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, val+";8~")
- t.prepareKeyMod(key, ModMeta, val+";9~")
- t.prepareKeyMod(key, ModMeta|ModShift, val+";10~")
- t.prepareKeyMod(key, ModMeta|ModAlt, val+";11~")
- t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, val+";12~")
- t.prepareKeyMod(key, ModMeta|ModCtrl, val+";13~")
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, val+";14~")
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, val+";15~")
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, val+";16~")
- } else if strings.HasPrefix(val, "\x1bO") && len(val) == 3 {
- val = val[2:]
- t.prepareKeyModReplace(key, key+12, ModShift, "\x1b[1;2"+val)
- t.prepareKeyModReplace(key, key+48, ModAlt, "\x1b[1;3"+val)
- t.prepareKeyModReplace(key, key+24, ModCtrl, "\x1b[1;5"+val)
- t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, "\x1b[1;6"+val)
- t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, "\x1b[1;4"+val)
- t.prepareKeyMod(key, ModAlt|ModCtrl, "\x1b[1;7"+val)
- t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, "\x1b[1;8"+val)
- t.prepareKeyMod(key, ModMeta, "\x1b[1;9"+val)
- t.prepareKeyMod(key, ModMeta|ModShift, "\x1b[1;10"+val)
- t.prepareKeyMod(key, ModMeta|ModAlt, "\x1b[1;11"+val)
- t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, "\x1b[1;12"+val)
- t.prepareKeyMod(key, ModMeta|ModCtrl, "\x1b[1;13"+val)
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, "\x1b[1;14"+val)
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, "\x1b[1;15"+val)
- t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, "\x1b[1;16"+val)
- }
- }
- func (t *tScreen) prepareXtermModifiers() {
- if t.ti.Modifiers != terminfo.ModifiersXTerm {
- return
- }
- t.prepareKeyModXTerm(KeyRight, t.ti.KeyRight)
- t.prepareKeyModXTerm(KeyLeft, t.ti.KeyLeft)
- t.prepareKeyModXTerm(KeyUp, t.ti.KeyUp)
- t.prepareKeyModXTerm(KeyDown, t.ti.KeyDown)
- t.prepareKeyModXTerm(KeyInsert, t.ti.KeyInsert)
- t.prepareKeyModXTerm(KeyDelete, t.ti.KeyDelete)
- t.prepareKeyModXTerm(KeyPgUp, t.ti.KeyPgUp)
- t.prepareKeyModXTerm(KeyPgDn, t.ti.KeyPgDn)
- t.prepareKeyModXTerm(KeyHome, t.ti.KeyHome)
- t.prepareKeyModXTerm(KeyEnd, t.ti.KeyEnd)
- t.prepareKeyModXTerm(KeyF1, t.ti.KeyF1)
- t.prepareKeyModXTerm(KeyF2, t.ti.KeyF2)
- t.prepareKeyModXTerm(KeyF3, t.ti.KeyF3)
- t.prepareKeyModXTerm(KeyF4, t.ti.KeyF4)
- t.prepareKeyModXTerm(KeyF5, t.ti.KeyF5)
- t.prepareKeyModXTerm(KeyF6, t.ti.KeyF6)
- t.prepareKeyModXTerm(KeyF7, t.ti.KeyF7)
- t.prepareKeyModXTerm(KeyF8, t.ti.KeyF8)
- t.prepareKeyModXTerm(KeyF9, t.ti.KeyF9)
- t.prepareKeyModXTerm(KeyF10, t.ti.KeyF10)
- t.prepareKeyModXTerm(KeyF11, t.ti.KeyF11)
- t.prepareKeyModXTerm(KeyF12, t.ti.KeyF12)
- }
- func (t *tScreen) prepareBracketedPaste() {
- // Another workaround for lack of reporting in terminfo.
- // We assume if the terminal has a mouse entry, that it
- // offers bracketed paste. But we allow specific overrides
- // via our terminal database.
- if t.ti.EnablePaste != "" {
- t.enablePaste = t.ti.EnablePaste
- t.disablePaste = t.ti.DisablePaste
- t.prepareKey(keyPasteStart, t.ti.PasteStart)
- t.prepareKey(keyPasteEnd, t.ti.PasteEnd)
- } else if t.ti.Mouse != "" {
- t.enablePaste = "\x1b[?2004h"
- t.disablePaste = "\x1b[?2004l"
- t.prepareKey(keyPasteStart, "\x1b[200~")
- t.prepareKey(keyPasteEnd, "\x1b[201~")
- }
- }
- func (t *tScreen) prepareExtendedOSC() {
- // Linux is a special beast - because it has a mouse entry, but does
- // not swallow these OSC commands properly.
- if (strings.Contains(t.ti.Name, "linux")) {
- return;
- }
- // More stuff for limits in terminfo. This time we are applying
- // the most common OSC (operating system commands). Generally
- // terminals that don't understand these will ignore them.
- // Again, we condition this based on mouse capabilities.
- if t.ti.EnterUrl != "" {
- t.enterUrl = t.ti.EnterUrl
- t.exitUrl = t.ti.ExitUrl
- } else if t.ti.Mouse != "" {
- t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\"
- t.exitUrl = "\x1b]8;;\x1b\\"
- }
- if t.ti.SetWindowSize != "" {
- t.setWinSize = t.ti.SetWindowSize
- } else if t.ti.Mouse != "" {
- t.setWinSize = "\x1b[8;%p1%p2%d;%dt"
- }
- }
- func (t *tScreen) prepareCursorStyles() {
- // Another workaround for lack of reporting in terminfo.
- // We assume if the terminal has a mouse entry, that it
- // offers bracketed paste. But we allow specific overrides
- // via our terminal database.
- if t.ti.CursorDefault != "" {
- t.cursorStyles = map[CursorStyle]string{
- CursorStyleDefault: t.ti.CursorDefault,
- CursorStyleBlinkingBlock: t.ti.CursorBlinkingBlock,
- CursorStyleSteadyBlock: t.ti.CursorSteadyBlock,
- CursorStyleBlinkingUnderline: t.ti.CursorBlinkingUnderline,
- CursorStyleSteadyUnderline: t.ti.CursorSteadyUnderline,
- CursorStyleBlinkingBar: t.ti.CursorBlinkingBar,
- CursorStyleSteadyBar: t.ti.CursorSteadyBar,
- }
- } else if t.ti.Mouse != "" {
- t.cursorStyles = map[CursorStyle]string{
- CursorStyleDefault: "\x1b[0 q",
- CursorStyleBlinkingBlock: "\x1b[1 q",
- CursorStyleSteadyBlock: "\x1b[2 q",
- CursorStyleBlinkingUnderline: "\x1b[3 q",
- CursorStyleSteadyUnderline: "\x1b[4 q",
- CursorStyleBlinkingBar: "\x1b[5 q",
- CursorStyleSteadyBar: "\x1b[6 q",
- }
- }
- }
- func (t *tScreen) prepareKey(key Key, val string) {
- t.prepareKeyMod(key, ModNone, val)
- }
- func (t *tScreen) prepareKeys() {
- ti := t.ti
- t.prepareKey(KeyBackspace, ti.KeyBackspace)
- t.prepareKey(KeyF1, ti.KeyF1)
- t.prepareKey(KeyF2, ti.KeyF2)
- t.prepareKey(KeyF3, ti.KeyF3)
- t.prepareKey(KeyF4, ti.KeyF4)
- t.prepareKey(KeyF5, ti.KeyF5)
- t.prepareKey(KeyF6, ti.KeyF6)
- t.prepareKey(KeyF7, ti.KeyF7)
- t.prepareKey(KeyF8, ti.KeyF8)
- t.prepareKey(KeyF9, ti.KeyF9)
- t.prepareKey(KeyF10, ti.KeyF10)
- t.prepareKey(KeyF11, ti.KeyF11)
- t.prepareKey(KeyF12, ti.KeyF12)
- t.prepareKey(KeyF13, ti.KeyF13)
- t.prepareKey(KeyF14, ti.KeyF14)
- t.prepareKey(KeyF15, ti.KeyF15)
- t.prepareKey(KeyF16, ti.KeyF16)
- t.prepareKey(KeyF17, ti.KeyF17)
- t.prepareKey(KeyF18, ti.KeyF18)
- t.prepareKey(KeyF19, ti.KeyF19)
- t.prepareKey(KeyF20, ti.KeyF20)
- t.prepareKey(KeyF21, ti.KeyF21)
- t.prepareKey(KeyF22, ti.KeyF22)
- t.prepareKey(KeyF23, ti.KeyF23)
- t.prepareKey(KeyF24, ti.KeyF24)
- t.prepareKey(KeyF25, ti.KeyF25)
- t.prepareKey(KeyF26, ti.KeyF26)
- t.prepareKey(KeyF27, ti.KeyF27)
- t.prepareKey(KeyF28, ti.KeyF28)
- t.prepareKey(KeyF29, ti.KeyF29)
- t.prepareKey(KeyF30, ti.KeyF30)
- t.prepareKey(KeyF31, ti.KeyF31)
- t.prepareKey(KeyF32, ti.KeyF32)
- t.prepareKey(KeyF33, ti.KeyF33)
- t.prepareKey(KeyF34, ti.KeyF34)
- t.prepareKey(KeyF35, ti.KeyF35)
- t.prepareKey(KeyF36, ti.KeyF36)
- t.prepareKey(KeyF37, ti.KeyF37)
- t.prepareKey(KeyF38, ti.KeyF38)
- t.prepareKey(KeyF39, ti.KeyF39)
- t.prepareKey(KeyF40, ti.KeyF40)
- t.prepareKey(KeyF41, ti.KeyF41)
- t.prepareKey(KeyF42, ti.KeyF42)
- t.prepareKey(KeyF43, ti.KeyF43)
- t.prepareKey(KeyF44, ti.KeyF44)
- t.prepareKey(KeyF45, ti.KeyF45)
- t.prepareKey(KeyF46, ti.KeyF46)
- t.prepareKey(KeyF47, ti.KeyF47)
- t.prepareKey(KeyF48, ti.KeyF48)
- t.prepareKey(KeyF49, ti.KeyF49)
- t.prepareKey(KeyF50, ti.KeyF50)
- t.prepareKey(KeyF51, ti.KeyF51)
- t.prepareKey(KeyF52, ti.KeyF52)
- t.prepareKey(KeyF53, ti.KeyF53)
- t.prepareKey(KeyF54, ti.KeyF54)
- t.prepareKey(KeyF55, ti.KeyF55)
- t.prepareKey(KeyF56, ti.KeyF56)
- t.prepareKey(KeyF57, ti.KeyF57)
- t.prepareKey(KeyF58, ti.KeyF58)
- t.prepareKey(KeyF59, ti.KeyF59)
- t.prepareKey(KeyF60, ti.KeyF60)
- t.prepareKey(KeyF61, ti.KeyF61)
- t.prepareKey(KeyF62, ti.KeyF62)
- t.prepareKey(KeyF63, ti.KeyF63)
- t.prepareKey(KeyF64, ti.KeyF64)
- t.prepareKey(KeyInsert, ti.KeyInsert)
- t.prepareKey(KeyDelete, ti.KeyDelete)
- t.prepareKey(KeyHome, ti.KeyHome)
- t.prepareKey(KeyEnd, ti.KeyEnd)
- t.prepareKey(KeyUp, ti.KeyUp)
- t.prepareKey(KeyDown, ti.KeyDown)
- t.prepareKey(KeyLeft, ti.KeyLeft)
- t.prepareKey(KeyRight, ti.KeyRight)
- t.prepareKey(KeyPgUp, ti.KeyPgUp)
- t.prepareKey(KeyPgDn, ti.KeyPgDn)
- t.prepareKey(KeyHelp, ti.KeyHelp)
- t.prepareKey(KeyPrint, ti.KeyPrint)
- t.prepareKey(KeyCancel, ti.KeyCancel)
- t.prepareKey(KeyExit, ti.KeyExit)
- t.prepareKey(KeyBacktab, ti.KeyBacktab)
- t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight)
- t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft)
- t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp)
- t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown)
- t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome)
- t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd)
- t.prepareKeyMod(KeyPgUp, ModShift, ti.KeyShfPgUp)
- t.prepareKeyMod(KeyPgDn, ModShift, ti.KeyShfPgDn)
- t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight)
- t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft)
- t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp)
- t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown)
- t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome)
- t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd)
- // Sadly, xterm handling of keycodes is somewhat erratic. In
- // particular, different codes are sent depending on application
- // mode is in use or not, and the entries for many of these are
- // simply absent from terminfo on many systems. So we insert
- // a number of escape sequences if they are not already used, in
- // order to have the widest correct usage. Note that prepareKey
- // will not inject codes if the escape sequence is already known.
- // We also only do this for terminals that have the application
- // mode present.
- // Cursor mode
- if ti.EnterKeypad != "" {
- t.prepareKey(KeyUp, "\x1b[A")
- t.prepareKey(KeyDown, "\x1b[B")
- t.prepareKey(KeyRight, "\x1b[C")
- t.prepareKey(KeyLeft, "\x1b[D")
- t.prepareKey(KeyEnd, "\x1b[F")
- t.prepareKey(KeyHome, "\x1b[H")
- t.prepareKey(KeyDelete, "\x1b[3~")
- t.prepareKey(KeyHome, "\x1b[1~")
- t.prepareKey(KeyEnd, "\x1b[4~")
- t.prepareKey(KeyPgUp, "\x1b[5~")
- t.prepareKey(KeyPgDn, "\x1b[6~")
- // Application mode
- t.prepareKey(KeyUp, "\x1bOA")
- t.prepareKey(KeyDown, "\x1bOB")
- t.prepareKey(KeyRight, "\x1bOC")
- t.prepareKey(KeyLeft, "\x1bOD")
- t.prepareKey(KeyHome, "\x1bOH")
- }
- t.prepareKey(keyPasteStart, ti.PasteStart)
- t.prepareKey(keyPasteEnd, ti.PasteEnd)
- t.prepareXtermModifiers()
- t.prepareBracketedPaste()
- t.prepareCursorStyles()
- t.prepareExtendedOSC()
- outer:
- // Add key mappings for control keys.
- for i := 0; i < ' '; i++ {
- // Do not insert direct key codes for ambiguous keys.
- // For example, ESC is used for lots of other keys, so
- // when parsing this we don't want to fast path handling
- // of it, but instead wait a bit before parsing it as in
- // isolation.
- for esc := range t.keycodes {
- if []byte(esc)[0] == byte(i) {
- continue outer
- }
- }
- t.keyexist[Key(i)] = true
- mod := ModCtrl
- switch Key(i) {
- case KeyBS, KeyTAB, KeyESC, KeyCR:
- // directly type-able- no control sequence
- mod = ModNone
- }
- t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod}
- }
- }
- func (t *tScreen) Fini() {
- t.finiOnce.Do(t.finish)
- }
- func (t *tScreen) finish() {
- close(t.quit)
- t.finalize()
- }
- func (t *tScreen) SetStyle(style Style) {
- t.Lock()
- if !t.fini {
- t.style = style
- }
- t.Unlock()
- }
- func (t *tScreen) Clear() {
- t.Fill(' ', t.style)
- }
- func (t *tScreen) Fill(r rune, style Style) {
- t.Lock()
- if !t.fini {
- t.cells.Fill(r, style)
- }
- t.Unlock()
- }
- func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
- t.Lock()
- if !t.fini {
- t.cells.SetContent(x, y, mainc, combc, style)
- }
- t.Unlock()
- }
- func (t *tScreen) GetContent(x, y int) (rune, []rune, Style, int) {
- t.Lock()
- mainc, combc, style, width := t.cells.GetContent(x, y)
- t.Unlock()
- return mainc, combc, style, width
- }
- func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) {
- if len(ch) > 0 {
- t.SetContent(x, y, ch[0], ch[1:], style)
- } else {
- t.SetContent(x, y, ' ', nil, style)
- }
- }
- func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
- nb := make([]byte, 6)
- ob := make([]byte, 6)
- num := utf8.EncodeRune(ob, r)
- ob = ob[:num]
- dst := 0
- var err error
- if enc := t.encoder; enc != nil {
- enc.Reset()
- dst, _, err = enc.Transform(nb, ob, true)
- }
- if err != nil || dst == 0 || nb[0] == '\x1a' {
- // Combining characters are elided
- if len(buf) == 0 {
- if acs, ok := t.acs[r]; ok {
- buf = append(buf, []byte(acs)...)
- } else if fb, ok := t.fallback[r]; ok {
- buf = append(buf, []byte(fb)...)
- } else {
- buf = append(buf, '?')
- }
- }
- } else {
- buf = append(buf, nb[:dst]...)
- }
- return buf
- }
- func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
- ti := t.ti
- if ti.Colors == 0 {
- // foreground vs background, we calculate luminance
- // and possibly do a reverse video
- if !fg.Valid() {
- return attr
- }
- v, ok := t.colors[fg]
- if !ok {
- v = FindColor(fg, []Color{ColorBlack, ColorWhite})
- t.colors[fg] = v
- }
- switch v {
- case ColorWhite:
- return attr
- case ColorBlack:
- return attr ^ AttrReverse
- }
- }
- if fg == ColorReset || bg == ColorReset {
- t.TPuts(ti.ResetFgBg)
- }
- if t.truecolor {
- if ti.SetFgBgRGB != "" && fg.IsRGB() && bg.IsRGB() {
- r1, g1, b1 := fg.RGB()
- r2, g2, b2 := bg.RGB()
- t.TPuts(ti.TParm(ti.SetFgBgRGB,
- int(r1), int(g1), int(b1),
- int(r2), int(g2), int(b2)))
- return attr
- }
- if fg.IsRGB() && ti.SetFgRGB != "" {
- r, g, b := fg.RGB()
- t.TPuts(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b)))
- fg = ColorDefault
- }
- if bg.IsRGB() && ti.SetBgRGB != "" {
- r, g, b := bg.RGB()
- t.TPuts(ti.TParm(ti.SetBgRGB,
- int(r), int(g), int(b)))
- bg = ColorDefault
- }
- }
- if fg.Valid() {
- if v, ok := t.colors[fg]; ok {
- fg = v
- } else {
- v = FindColor(fg, t.palette)
- t.colors[fg] = v
- fg = v
- }
- }
- if bg.Valid() {
- if v, ok := t.colors[bg]; ok {
- bg = v
- } else {
- v = FindColor(bg, t.palette)
- t.colors[bg] = v
- bg = v
- }
- }
- if fg.Valid() && bg.Valid() && ti.SetFgBg != "" {
- t.TPuts(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff)))
- } else {
- if fg.Valid() && ti.SetFg != "" {
- t.TPuts(ti.TParm(ti.SetFg, int(fg&0xff)))
- }
- if bg.Valid() && ti.SetBg != "" {
- t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff)))
- }
- }
- return attr
- }
- func (t *tScreen) drawCell(x, y int) int {
- ti := t.ti
- mainc, combc, style, width := t.cells.GetContent(x, y)
- if !t.cells.Dirty(x, y) {
- return width
- }
- if y == t.h-1 && x == t.w-1 && t.ti.AutoMargin && ti.InsertChar != "" {
- // our solution is somewhat goofy.
- // we write to the second to the last cell what we want in the last cell, then we
- // insert a character at that 2nd to last position to shift the last column into
- // place, then we rewrite that 2nd to last cell. Old terminals suck.
- t.TPuts(ti.TGoto(x-1, y))
- defer func() {
- t.TPuts(ti.TGoto(x-1, y))
- t.TPuts(ti.InsertChar)
- t.cy = y
- t.cx = x - 1
- t.cells.SetDirty(x-1, y, true)
- _ = t.drawCell(x-1, y)
- t.TPuts(t.ti.TGoto(0, 0))
- t.cy = 0
- t.cx = 0
- }()
- } else if t.cy != y || t.cx != x {
- t.TPuts(ti.TGoto(x, y))
- t.cx = x
- t.cy = y
- }
- if style == StyleDefault {
- style = t.style
- }
- if style != t.curstyle {
- fg, bg, attrs := style.Decompose()
- t.TPuts(ti.AttrOff)
- attrs = t.sendFgBg(fg, bg, attrs)
- if attrs&AttrBold != 0 {
- t.TPuts(ti.Bold)
- }
- if attrs&AttrUnderline != 0 {
- t.TPuts(ti.Underline)
- }
- if attrs&AttrReverse != 0 {
- t.TPuts(ti.Reverse)
- }
- if attrs&AttrBlink != 0 {
- t.TPuts(ti.Blink)
- }
- if attrs&AttrDim != 0 {
- t.TPuts(ti.Dim)
- }
- if attrs&AttrItalic != 0 {
- t.TPuts(ti.Italic)
- }
- if attrs&AttrStrikeThrough != 0 {
- t.TPuts(ti.StrikeThrough)
- }
- // URL string can be long, so don't send it unless we really need to
- if t.enterUrl != "" && t.curstyle != style {
- if style.url != "" {
- t.TPuts(ti.TParm(t.enterUrl, style.url, style.urlId))
- } else {
- t.TPuts(t.exitUrl)
- }
- }
- t.curstyle = style
- }
- // now emit runes - taking care to not overrun width with a
- // wide character, and to ensure that we emit exactly one regular
- // character followed up by any residual combing characters
- if width < 1 {
- width = 1
- }
- var str string
- buf := make([]byte, 0, 6)
- buf = t.encodeRune(mainc, buf)
- for _, r := range combc {
- buf = t.encodeRune(r, buf)
- }
- str = string(buf)
- if width > 1 && str == "?" {
- // No FullWidth character support
- str = "? "
- t.cx = -1
- }
- if x > t.w-width {
- // too wide to fit; emit a single space instead
- width = 1
- str = " "
- }
- t.writeString(str)
- t.cx += width
- t.cells.SetDirty(x, y, false)
- if width > 1 {
- t.cx = -1
- }
- return width
- }
- func (t *tScreen) ShowCursor(x, y int) {
- t.Lock()
- t.cursorx = x
- t.cursory = y
- t.Unlock()
- }
- func (t *tScreen) SetCursorStyle(cs CursorStyle) {
- t.Lock()
- t.cursorStyle = cs
- t.Unlock()
- }
- func (t *tScreen) HideCursor() {
- t.ShowCursor(-1, -1)
- }
- func (t *tScreen) showCursor() {
- x, y := t.cursorx, t.cursory
- w, h := t.cells.Size()
- if x < 0 || y < 0 || x >= w || y >= h {
- t.hideCursor()
- return
- }
- t.TPuts(t.ti.TGoto(x, y))
- t.TPuts(t.ti.ShowCursor)
- if t.cursorStyles != nil {
- if esc, ok := t.cursorStyles[t.cursorStyle]; ok {
- t.TPuts(esc)
- }
- }
- t.cx = x
- t.cy = y
- }
- // writeString sends a string to the terminal. The string is sent as-is and
- // this function does not expand inline padding indications (of the form
- // $<[delay]> where [delay] is msec). In order to have these expanded, use
- // TPuts. If the screen is "buffering", the string is collected in a buffer,
- // with the intention that the entire buffer be sent to the terminal in one
- // write operation at some point later.
- func (t *tScreen) writeString(s string) {
- if t.buffering {
- _, _ = io.WriteString(&t.buf, s)
- } else {
- _, _ = io.WriteString(t.tty, s)
- }
- }
- func (t *tScreen) TPuts(s string) {
- if t.buffering {
- t.ti.TPuts(&t.buf, s)
- } else {
- t.ti.TPuts(t.tty, s)
- }
- }
- func (t *tScreen) Show() {
- t.Lock()
- if !t.fini {
- t.resize()
- t.draw()
- }
- t.Unlock()
- }
- func (t *tScreen) clearScreen() {
- t.TPuts(t.ti.AttrOff)
- t.TPuts(t.exitUrl)
- fg, bg, _ := t.style.Decompose()
- _ = t.sendFgBg(fg, bg, AttrNone)
- t.TPuts(t.ti.Clear)
- t.clear = false
- }
- func (t *tScreen) hideCursor() {
- // does not update cursor position
- if t.ti.HideCursor != "" {
- t.TPuts(t.ti.HideCursor)
- } else {
- // No way to hide cursor, stick it
- // at bottom right of screen
- t.cx, t.cy = t.cells.Size()
- t.TPuts(t.ti.TGoto(t.cx, t.cy))
- }
- }
- func (t *tScreen) draw() {
- // clobber cursor position, because we're going to change it all
- t.cx = -1
- t.cy = -1
- // make no style assumptions
- t.curstyle = styleInvalid
- t.buf.Reset()
- t.buffering = true
- defer func() {
- t.buffering = false
- }()
- // hide the cursor while we move stuff around
- t.hideCursor()
- if t.clear {
- t.clearScreen()
- }
- for y := 0; y < t.h; y++ {
- for x := 0; x < t.w; x++ {
- width := t.drawCell(x, y)
- if width > 1 {
- if x+1 < t.w {
- // this is necessary so that if we ever
- // go back to drawing that cell, we
- // actually will *draw* it.
- t.cells.SetDirty(x+1, y, true)
- }
- }
- x += width - 1
- }
- }
- // restore the cursor
- t.showCursor()
- _, _ = t.buf.WriteTo(t.tty)
- }
- func (t *tScreen) EnableMouse(flags ...MouseFlags) {
- var f MouseFlags
- flagsPresent := false
- for _, flag := range flags {
- f |= flag
- flagsPresent = true
- }
- if !flagsPresent {
- f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
- }
- t.Lock()
- t.mouseFlags = f
- t.enableMouse(f)
- t.Unlock()
- }
- func (t *tScreen) enableMouse(f MouseFlags) {
- // Rather than using terminfo to find mouse escape sequences, we rely on the fact that
- // pretty much *every* terminal that supports mouse tracking follows the
- // XTerm standards (the modern ones).
- if len(t.mouse) != 0 {
- // start by disabling all tracking.
- t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l")
- if f&MouseButtonEvents != 0 {
- t.TPuts("\x1b[?1000h")
- }
- if f&MouseDragEvents != 0 {
- t.TPuts("\x1b[?1002h")
- }
- if f&MouseMotionEvents != 0 {
- t.TPuts("\x1b[?1003h")
- }
- if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 {
- t.TPuts("\x1b[?1006h")
- }
- }
- }
- func (t *tScreen) DisableMouse() {
- t.Lock()
- t.mouseFlags = 0
- t.enableMouse(0)
- t.Unlock()
- }
- func (t *tScreen) EnablePaste() {
- t.Lock()
- t.pasteEnabled = true
- t.enablePasting(true)
- t.Unlock()
- }
- func (t *tScreen) DisablePaste() {
- t.Lock()
- t.pasteEnabled = false
- t.enablePasting(false)
- t.Unlock()
- }
- func (t *tScreen) enablePasting(on bool) {
- var s string
- if on {
- s = t.enablePaste
- } else {
- s = t.disablePaste
- }
- if s != "" {
- t.TPuts(s)
- }
- }
- func (t *tScreen) Size() (int, int) {
- t.Lock()
- w, h := t.w, t.h
- t.Unlock()
- return w, h
- }
- func (t *tScreen) resize() {
- if w, h, e := t.tty.WindowSize(); e == nil {
- if w != t.w || h != t.h {
- t.cx = -1
- t.cy = -1
- t.cells.Resize(w, h)
- t.cells.Invalidate()
- t.h = h
- t.w = w
- ev := NewEventResize(w, h)
- _ = t.PostEvent(ev)
- }
- }
- }
- func (t *tScreen) Colors() int {
- // this doesn't change, no need for lock
- if t.truecolor {
- return 1 << 24
- }
- return t.ti.Colors
- }
- // nColors returns the size of the built-in palette.
- // This is distinct from Colors(), as it will generally
- // always be a small number. (<= 256)
- func (t *tScreen) nColors() int {
- return t.ti.Colors
- }
- func (t *tScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
- defer close(ch)
- for {
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ev := <-t.evch:
- select {
- case <-quit:
- return
- case <-t.quit:
- return
- case ch <- ev:
- }
- }
- }
- }
- func (t *tScreen) PollEvent() Event {
- select {
- case <-t.quit:
- return nil
- case ev := <-t.evch:
- return ev
- }
- }
- func (t *tScreen) HasPendingEvent() bool {
- return len(t.evch) > 0
- }
- // vtACSNames is a map of bytes defined by terminfo that are used in
- // the terminals Alternate Character Set to represent other glyphs.
- // For example, the upper left corner of the box drawing set can be
- // displayed by printing "l" while in the alternate character set.
- // It's not quite that simple, since the "l" is the terminfo name,
- // and it may be necessary to use a different character based on
- // the terminal implementation (or the terminal may lack support for
- // this altogether). See buildAcsMap below for detail.
- var vtACSNames = map[byte]rune{
- '+': RuneRArrow,
- ',': RuneLArrow,
- '-': RuneUArrow,
- '.': RuneDArrow,
- '0': RuneBlock,
- '`': RuneDiamond,
- 'a': RuneCkBoard,
- 'b': '␉', // VT100, Not defined by terminfo
- 'c': '␌', // VT100, Not defined by terminfo
- 'd': '␋', // VT100, Not defined by terminfo
- 'e': '␊', // VT100, Not defined by terminfo
- 'f': RuneDegree,
- 'g': RunePlMinus,
- 'h': RuneBoard,
- 'i': RuneLantern,
- 'j': RuneLRCorner,
- 'k': RuneURCorner,
- 'l': RuneULCorner,
- 'm': RuneLLCorner,
- 'n': RunePlus,
- 'o': RuneS1,
- 'p': RuneS3,
- 'q': RuneHLine,
- 'r': RuneS7,
- 's': RuneS9,
- 't': RuneLTee,
- 'u': RuneRTee,
- 'v': RuneBTee,
- 'w': RuneTTee,
- 'x': RuneVLine,
- 'y': RuneLEqual,
- 'z': RuneGEqual,
- '{': RunePi,
- '|': RuneNEqual,
- '}': RuneSterling,
- '~': RuneBullet,
- }
- // buildAcsMap builds a map of characters that we translate from Unicode to
- // alternate character encodings. To do this, we use the standard VT100 ACS
- // maps. This is only done if the terminal lacks support for Unicode; we
- // always prefer to emit Unicode glyphs when we are able.
- func (t *tScreen) buildAcsMap() {
- acsstr := t.ti.AltChars
- t.acs = make(map[rune]string)
- for len(acsstr) > 2 {
- srcv := acsstr[0]
- dstv := string(acsstr[1])
- if r, ok := vtACSNames[srcv]; ok {
- t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs
- }
- acsstr = acsstr[2:]
- }
- }
- func (t *tScreen) PostEventWait(ev Event) {
- t.evch <- ev
- }
- func (t *tScreen) PostEvent(ev Event) error {
- select {
- case t.evch <- ev:
- return nil
- default:
- return ErrEventQFull
- }
- }
- func (t *tScreen) clip(x, y int) (int, int) {
- w, h := t.cells.Size()
- if x < 0 {
- x = 0
- }
- if y < 0 {
- y = 0
- }
- if x > w-1 {
- x = w - 1
- }
- if y > h-1 {
- y = h - 1
- }
- return x, y
- }
- // buildMouseEvent returns an event based on the supplied coordinates and button
- // state. Note that the screen's mouse button state is updated based on the
- // input to this function (i.e. it mutates the receiver).
- func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse {
- // XTerm mouse events only report at most one button at a time,
- // which may include a wheel button. Wheel motion events are
- // reported as single impulses, while other button events are reported
- // as separate press & release events.
- button := ButtonNone
- mod := ModNone
- // Mouse wheel has bit 6 set, no release events. It should be noted
- // that wheel events are sometimes misdelivered as mouse button events
- // during a click-drag, so we debounce these, considering them to be
- // button press events unless we see an intervening release event.
- switch btn & 0x43 {
- case 0:
- button = Button1
- case 1:
- button = Button3 // Note we prefer to treat right as button 2
- case 2:
- button = Button2 // And the middle button as button 3
- case 3:
- button = ButtonNone
- case 0x40:
- button = WheelUp
- case 0x41:
- button = WheelDown
- }
- if btn&0x4 != 0 {
- mod |= ModShift
- }
- if btn&0x8 != 0 {
- mod |= ModAlt
- }
- if btn&0x10 != 0 {
- mod |= ModCtrl
- }
- // Some terminals will report mouse coordinates outside the
- // screen, especially with click-drag events. Clip the coordinates
- // to the screen in that case.
- x, y = t.clip(x, y)
- return NewEventMouse(x, y, button, mod)
- }
- // parseSgrMouse attempts to locate an SGR mouse record at the start of the
- // buffer. It returns true, true if it found one, and the associated bytes
- // be removed from the buffer. It returns true, false if the buffer might
- // contain such an event, but more bytes are necessary (partial match), and
- // false, false if the content is definitely *not* an SGR mouse record.
- func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
- b := buf.Bytes()
- var x, y, btn, state int
- dig := false
- neg := false
- motion := false
- i := 0
- val := 0
- for i = range b {
- switch b[i] {
- case '\x1b':
- if state != 0 {
- return false, false
- }
- state = 1
- case '\x9b':
- if state != 0 {
- return false, false
- }
- state = 2
- case '[':
- if state != 1 {
- return false, false
- }
- state = 2
- case '<':
- if state != 2 {
- return false, false
- }
- val = 0
- dig = false
- neg = false
- state = 3
- case '-':
- if state != 3 && state != 4 && state != 5 {
- return false, false
- }
- if dig || neg {
- return false, false
- }
- neg = true // stay in state
- case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
- if state != 3 && state != 4 && state != 5 {
- return false, false
- }
- val *= 10
- val += int(b[i] - '0')
- dig = true // stay in state
- case ';':
- if neg {
- val = -val
- }
- switch state {
- case 3:
- btn, val = val, 0
- neg, dig, state = false, false, 4
- case 4:
- x, val = val-1, 0
- neg, dig, state = false, false, 5
- default:
- return false, false
- }
- case 'm', 'M':
- if state != 5 {
- return false, false
- }
- if neg {
- val = -val
- }
- y = val - 1
- motion = (btn & 32) != 0
- btn &^= 32
- if b[i] == 'm' {
- // mouse release, clear all buttons
- btn |= 3
- btn &^= 0x40
- t.buttondn = false
- } else if motion {
- /*
- * Some broken terminals appear to send
- * mouse button one motion events, instead of
- * encoding 35 (no buttons) into these events.
- * We resolve these by looking for a non-motion
- * event first.
- */
- if !t.buttondn {
- btn |= 3
- btn &^= 0x40
- }
- } else {
- t.buttondn = true
- }
- // consume the event bytes
- for i >= 0 {
- _, _ = buf.ReadByte()
- i--
- }
- *evs = append(*evs, t.buildMouseEvent(x, y, btn))
- return true, true
- }
- }
- // incomplete & inconclusive at this point
- return true, false
- }
- // parseXtermMouse is like parseSgrMouse, but it parses a legacy
- // X11 mouse record.
- func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
- b := buf.Bytes()
- state := 0
- btn := 0
- x := 0
- y := 0
- for i := range b {
- switch state {
- case 0:
- switch b[i] {
- case '\x1b':
- state = 1
- case '\x9b':
- state = 2
- default:
- return false, false
- }
- case 1:
- if b[i] != '[' {
- return false, false
- }
- state = 2
- case 2:
- if b[i] != 'M' {
- return false, false
- }
- state++
- case 3:
- btn = int(b[i])
- state++
- case 4:
- x = int(b[i]) - 32 - 1
- state++
- case 5:
- y = int(b[i]) - 32 - 1
- for i >= 0 {
- _, _ = buf.ReadByte()
- i--
- }
- *evs = append(*evs, t.buildMouseEvent(x, y, btn))
- return true, true
- }
- }
- return true, false
- }
- func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
- b := buf.Bytes()
- partial := false
- for e, k := range t.keycodes {
- esc := []byte(e)
- if (len(esc) == 1) && (esc[0] == '\x1b') {
- continue
- }
- if bytes.HasPrefix(b, esc) {
- // matched
- var r rune
- if len(esc) == 1 {
- r = rune(b[0])
- }
- mod := k.mod
- if t.escaped {
- mod |= ModAlt
- t.escaped = false
- }
- switch k.key {
- case keyPasteStart:
- *evs = append(*evs, NewEventPaste(true))
- case keyPasteEnd:
- *evs = append(*evs, NewEventPaste(false))
- default:
- *evs = append(*evs, NewEventKey(k.key, r, mod))
- }
- for i := 0; i < len(esc); i++ {
- _, _ = buf.ReadByte()
- }
- return true, true
- }
- if bytes.HasPrefix(esc, b) {
- partial = true
- }
- }
- return partial, false
- }
- func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
- b := buf.Bytes()
- if b[0] >= ' ' && b[0] <= 0x7F {
- // printable ASCII easy to deal with -- no encodings
- mod := ModNone
- if t.escaped {
- mod = ModAlt
- t.escaped = false
- }
- *evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod))
- _, _ = buf.ReadByte()
- return true, true
- }
- if b[0] < 0x80 {
- // Low numbered values are control keys, not runes.
- return false, false
- }
- utf := make([]byte, 12)
- for l := 1; l <= len(b); l++ {
- t.decoder.Reset()
- nOut, nIn, e := t.decoder.Transform(utf, b[:l], true)
- if e == transform.ErrShortSrc {
- continue
- }
- if nOut != 0 {
- r, _ := utf8.DecodeRune(utf[:nOut])
- if r != utf8.RuneError {
- mod := ModNone
- if t.escaped {
- mod = ModAlt
- t.escaped = false
- }
- *evs = append(*evs, NewEventKey(KeyRune, r, mod))
- }
- for nIn > 0 {
- _, _ = buf.ReadByte()
- nIn--
- }
- return true, true
- }
- }
- // Looks like potential escape
- return true, false
- }
- func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
- evs := t.collectEventsFromInput(buf, expire)
- for _, ev := range evs {
- t.PostEventWait(ev)
- }
- }
- // Return an array of Events extracted from the supplied buffer. This is done
- // while holding the screen's lock - the events can then be queued for
- // application processing with the lock released.
- func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event {
- res := make([]Event, 0, 20)
- t.Lock()
- defer t.Unlock()
- for {
- b := buf.Bytes()
- if len(b) == 0 {
- buf.Reset()
- return res
- }
- partials := 0
- if part, comp := t.parseRune(buf, &res); comp {
- continue
- } else if part {
- partials++
- }
- if part, comp := t.parseFunctionKey(buf, &res); comp {
- continue
- } else if part {
- partials++
- }
- // Only parse mouse records if this term claims to have
- // mouse support
- if t.ti.Mouse != "" {
- if part, comp := t.parseXtermMouse(buf, &res); comp {
- continue
- } else if part {
- partials++
- }
- if part, comp := t.parseSgrMouse(buf, &res); comp {
- continue
- } else if part {
- partials++
- }
- }
- if partials == 0 || expire {
- if b[0] == '\x1b' {
- if len(b) == 1 {
- res = append(res, NewEventKey(KeyEsc, 0, ModNone))
- t.escaped = false
- } else {
- t.escaped = true
- }
- _, _ = buf.ReadByte()
- continue
- }
- // Nothing was going to match, or we timed out
- // waiting for more data -- just deliver the characters
- // to the app & let them sort it out. Possibly we
- // should only do this for control characters like ESC.
- by, _ := buf.ReadByte()
- mod := ModNone
- if t.escaped {
- t.escaped = false
- mod = ModAlt
- }
- res = append(res, NewEventKey(KeyRune, rune(by), mod))
- continue
- }
- // well we have some partial data, wait until we get
- // some more
- break
- }
- return res
- }
- func (t *tScreen) mainLoop(stopQ chan struct{}) {
- defer t.wg.Done()
- buf := &bytes.Buffer{}
- for {
- select {
- case <-stopQ:
- return
- case <-t.quit:
- return
- case <-t.resizeQ:
- t.Lock()
- t.cx = -1
- t.cy = -1
- t.resize()
- t.cells.Invalidate()
- t.draw()
- t.Unlock()
- continue
- case <-t.keytimer.C:
- // If the timer fired, and the current time
- // is after the expiration of the escape sequence,
- // then we assume the escape sequence reached its
- // conclusion, and process the chunk independently.
- // This lets us detect conflicts such as a lone ESC.
- if buf.Len() > 0 {
- if time.Now().After(t.keyexpire) {
- t.scanInput(buf, true)
- }
- }
- if buf.Len() > 0 {
- if !t.keytimer.Stop() {
- select {
- case <-t.keytimer.C:
- default:
- }
- }
- t.keytimer.Reset(time.Millisecond * 50)
- }
- case chunk := <-t.keychan:
- buf.Write(chunk)
- t.keyexpire = time.Now().Add(time.Millisecond * 50)
- t.scanInput(buf, false)
- if !t.keytimer.Stop() {
- select {
- case <-t.keytimer.C:
- default:
- }
- }
- if buf.Len() > 0 {
- t.keytimer.Reset(time.Millisecond * 50)
- }
- }
- }
- }
- func (t *tScreen) inputLoop(stopQ chan struct{}) {
- defer t.wg.Done()
- for {
- select {
- case <-stopQ:
- return
- default:
- }
- chunk := make([]byte, 128)
- n, e := t.tty.Read(chunk)
- switch e {
- case nil:
- default:
- t.Lock()
- running := t.running
- t.Unlock()
- if running {
- _ = t.PostEvent(NewEventError(e))
- }
- return
- }
- if n > 0 {
- t.keychan <- chunk[:n]
- }
- }
- }
- func (t *tScreen) Sync() {
- t.Lock()
- t.cx = -1
- t.cy = -1
- if !t.fini {
- t.resize()
- t.clear = true
- t.cells.Invalidate()
- t.draw()
- }
- t.Unlock()
- }
- func (t *tScreen) CharacterSet() string {
- return t.charset
- }
- func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) {
- t.Lock()
- t.fallback[orig] = fallback
- t.Unlock()
- }
- func (t *tScreen) UnregisterRuneFallback(orig rune) {
- t.Lock()
- delete(t.fallback, orig)
- t.Unlock()
- }
- func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
- if enc := t.encoder; enc != nil {
- nb := make([]byte, 6)
- ob := make([]byte, 6)
- num := utf8.EncodeRune(ob, r)
- enc.Reset()
- dst, _, err := enc.Transform(nb, ob[:num], true)
- if dst != 0 && err == nil && nb[0] != '\x1A' {
- return true
- }
- }
- // Terminal fallbacks always permitted, since we assume they are
- // basically nearly perfect renditions.
- if _, ok := t.acs[r]; ok {
- return true
- }
- if !checkFallbacks {
- return false
- }
- if _, ok := t.fallback[r]; ok {
- return true
- }
- return false
- }
- func (t *tScreen) HasMouse() bool {
- return len(t.mouse) != 0
- }
- func (t *tScreen) HasKey(k Key) bool {
- if k == KeyRune {
- return true
- }
- return t.keyexist[k]
- }
- func (t *tScreen) SetSize(w, h int) {
- if t.setWinSize != "" {
- t.TPuts(t.ti.TParm(t.setWinSize, w, h))
- }
- t.cells.Invalidate()
- t.resize()
- }
- func (t *tScreen) Resize(int, int, int, int) {}
- func (t *tScreen) Suspend() error {
- t.disengage()
- return nil
- }
- func (t *tScreen) Resume() error {
- return t.engage()
- }
- // engage is used to place the terminal in raw mode and establish screen size, etc.
- // Think of this is as tcell "engaging" the clutch, as it's going to be driving the
- // terminal interface.
- func (t *tScreen) engage() error {
- t.Lock()
- defer t.Unlock()
- if t.tty == nil {
- return ErrNoScreen
- }
- t.tty.NotifyResize(func() {
- select {
- case t.resizeQ <- true:
- default:
- }
- })
- if t.running {
- return errors.New("already engaged")
- }
- if err := t.tty.Start(); err != nil {
- return err
- }
- t.running = true
- if w, h, err := t.tty.WindowSize(); err == nil && w != 0 && h != 0 {
- t.cells.Resize(w, h)
- }
- stopQ := make(chan struct{})
- t.stopQ = stopQ
- t.enableMouse(t.mouseFlags)
- t.enablePasting(t.pasteEnabled)
- ti := t.ti
- t.TPuts(ti.EnterCA)
- t.TPuts(ti.EnterKeypad)
- t.TPuts(ti.HideCursor)
- t.TPuts(ti.EnableAcs)
- t.TPuts(ti.Clear)
- t.wg.Add(2)
- go t.inputLoop(stopQ)
- go t.mainLoop(stopQ)
- return nil
- }
- // disengage is used to release the terminal back to support from the caller.
- // Think of this as tcell disengaging the clutch, so that another application
- // can take over the terminal interface. This restores the TTY mode that was
- // present when the application was first started.
- func (t *tScreen) disengage() {
- t.Lock()
- if !t.running {
- t.Unlock()
- return
- }
- t.running = false
- stopQ := t.stopQ
- close(stopQ)
- _ = t.tty.Drain()
- t.Unlock()
- t.tty.NotifyResize(nil)
- // wait for everything to shut down
- t.wg.Wait()
- // shutdown the screen and disable special modes (e.g. mouse and bracketed paste)
- ti := t.ti
- t.cells.Resize(0, 0)
- t.TPuts(ti.ShowCursor)
- if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault {
- t.TPuts(t.cursorStyles[t.cursorStyle])
- }
- t.TPuts(ti.ResetFgBg)
- t.TPuts(ti.AttrOff)
- t.TPuts(ti.Clear)
- t.TPuts(ti.ExitCA)
- t.TPuts(ti.ExitKeypad)
- t.enableMouse(0)
- t.enablePasting(false)
- _ = t.tty.Stop()
- }
- // Beep emits a beep to the terminal.
- func (t *tScreen) Beep() error {
- t.writeString(string(byte(7)))
- return nil
- }
- // finalize is used to at application shutdown, and restores the terminal
- // to it's initial state. It should not be called more than once.
- func (t *tScreen) finalize() {
- t.disengage()
- _ = t.tty.Close()
- }
|