package tcell
import (
"bytes"
"encoding/base64"
"errors"
"io"
"os"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
"golang.org/x/term"
"golang.org/x/text/transform"
"github.com/gdamore/tcell/v2/terminfo"
)
func NewTerminfoScreen () (Screen , error ) {
return NewTerminfoScreenFromTty (nil )
}
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
}
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 &baseScreen {screenImpl : t }, nil
}
func NewTerminfoScreenFromTty (tty Tty ) (Screen , error ) {
return NewTerminfoScreenFromTtyTerminfo (tty , nil )
}
type tKeyCode struct {
key Key
mod ModMask
}
type tScreen struct {
ti *terminfo .Terminfo
tty Tty
h int
w int
fini bool
cells CellBuffer
buffering bool
buf bytes .Buffer
curstyle Style
style Style
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
enableFocus string
disableFocus string
doubleUnder string
curlyUnder string
dottedUnder string
dashedUnder string
underColor string
underRGB string
underFg string
cursorStyles map [CursorStyle ]string
cursorStyle CursorStyle
cursorColor Color
cursorRGB string
cursorFg string
saved *term .State
stopQ chan struct {}
eventQ chan Event
running bool
wg sync .WaitGroup
mouseFlags MouseFlags
pasteEnabled bool
focusEnabled bool
setTitle string
saveTitle string
restoreTitle string
title string
setClipboard string
sync .Mutex
}
func (t *tScreen ) Init () error {
if e := t .initialize (); e != nil {
return e
}
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
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
}
if os .Getenv ("TCELL_TRUECOLOR" ) == "disable" {
t .truecolor = false
}
nColors := t .nColors ()
if nColors > 256 {
nColors = 256
}
t .colors = make (map [Color ]Color , nColors )
t .palette = make ([]Color , nColors )
for i := 0 ; i < nColors ; i ++ {
t .palette [i ] = Color (i ) | ColorValid
t .colors [Color (i )|ColorValid ] = Color (i ) | ColorValid
}
t .quit = make (chan struct {})
t .eventQ = make (chan Event , 10 )
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 != "" {
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 != "" {
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 , "~" ) {
val = val [:len (val )-1 ]
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 () {
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 .ti .XTermLike {
t .enablePaste = "\x1b[?2004h"
t .disablePaste = "\x1b[?2004l"
t .prepareKey (keyPasteStart , "\x1b[200~" )
t .prepareKey (keyPasteEnd , "\x1b[201~" )
}
}
func (t *tScreen ) prepareUnderlines () {
if t .ti .DoubleUnderline != "" {
t .doubleUnder = t .ti .DoubleUnderline
} else if t .ti .XTermLike {
t .doubleUnder = "\x1b[4:2m"
}
if t .ti .CurlyUnderline != "" {
t .curlyUnder = t .ti .CurlyUnderline
} else if t .ti .XTermLike {
t .curlyUnder = "\x1b[4:3m"
}
if t .ti .DottedUnderline != "" {
t .dottedUnder = t .ti .DottedUnderline
} else if t .ti .XTermLike {
t .dottedUnder = "\x1b[4:4m"
}
if t .ti .DashedUnderline != "" {
t .dashedUnder = t .ti .DashedUnderline
} else if t .ti .XTermLike {
t .dashedUnder = "\x1b[4:5m"
}
if t .ti .UnderlineColor != "" {
t .underColor = t .ti .UnderlineColor
} else if t .curlyUnder != "" {
t .underColor = "\x1b[58:5:%p1%dm"
}
if t .ti .UnderlineColorRGB != "" {
t .underRGB = t .ti .UnderlineColorRGB
} else if t .underColor != "" {
t .underRGB = "\x1b[58:2::%p1%d:%p2%d:%p3%dm"
}
if t .ti .UnderlineColorReset != "" {
t .underFg = t .ti .UnderlineColorReset
} else if t .curlyUnder != "" {
t .underFg = "\x1b[59m"
}
}
func (t *tScreen ) prepareExtendedOSC () {
if strings .Contains (t .ti .Name , "linux" ) {
return
}
if t .ti .EnterUrl != "" {
t .enterUrl = t .ti .EnterUrl
t .exitUrl = t .ti .ExitUrl
} else if t .ti .Mouse != "" || t .ti .XTermLike {
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 .ti .XTermLike {
t .setWinSize = "\x1b[8;%p1%p2%d;%dt"
}
if t .ti .EnableFocusReporting != "" {
t .enableFocus = t .ti .EnableFocusReporting
} else if t .ti .Mouse != "" || t .ti .XTermLike {
t .enableFocus = "\x1b[?1004h"
}
if t .ti .DisableFocusReporting != "" {
t .disableFocus = t .ti .DisableFocusReporting
} else if t .ti .Mouse != "" || t .ti .XTermLike {
t .disableFocus = "\x1b[?1004l"
}
if t .ti .SetWindowTitle != "" {
t .setTitle = t .ti .SetWindowTitle
} else if t .ti .XTermLike {
t .saveTitle = "\x1b[22;2t"
t .restoreTitle = "\x1b[23;2t"
t .setTitle = "\x1b[>2t\x1b]2;%p1%s\x1b\\"
}
if t .setClipboard == "" && t .ti .XTermLike {
t .setClipboard = "\x1b]52;c;%p1%s\x1b\\"
}
}
func (t *tScreen ) prepareCursorStyles () {
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 .ti .XTermLike {
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" ,
}
}
if t .ti .CursorColorRGB != "" {
t .cursorRGB = t .ti .CursorColorRGB
}
if t .ti .CursorColorReset != "" {
t .cursorFg = t .ti .CursorColorReset
}
if t .cursorRGB == "" {
t .cursorRGB = "\x1b]12;%p1%s\007"
t .cursorFg = "\x1b]112\007"
}
t .cursorRGB = strings .Replace (t .cursorRGB , "%p1%s" , "#%p1%02x%p2%02x%p3%02x" , 1 )
}
func (t *tScreen ) prepareKey (key Key , val string ) {
t .prepareKeyMod (key , ModNone , val )
}
func (t *tScreen ) prepareKeys () {
ti := t .ti
if strings .HasPrefix (ti .Name , "xterm" ) {
t .ti .XTermLike = true
ti .XTermLike = true
}
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 )
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~" )
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 .prepareUnderlines ()
t .prepareExtendedOSC ()
outer :
for i := 0 ; i < ' ' ; i ++ {
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 :
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 ) 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' {
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 {
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 .DisableAutoMargin == "" && ti .InsertChar != "" {
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 .fg , style .bg , style .attrs
t .TPuts (ti .AttrOff )
attrs = t .sendFgBg (fg , bg , attrs )
if attrs &AttrBold != 0 {
t .TPuts (ti .Bold )
}
if us , uc := style .ulStyle , style .ulColor ; us != UnderlineStyleNone {
if t .underColor != "" || t .underRGB != "" {
if uc == ColorReset {
t .TPuts (t .underFg )
} else if uc .IsRGB () {
if t .underRGB != "" {
r , g , b := uc .RGB ()
t .TPuts (ti .TParm (t .underRGB , int (r ), int (g ), int (b )))
} else {
if v , ok := t .colors [uc ]; ok {
uc = v
} else {
v = FindColor (uc , t .palette )
t .colors [uc ] = v
uc = v
}
t .TPuts (ti .TParm (t .underColor , int (uc &0xff )))
}
} else if uc .Valid () {
t .TPuts (ti .TParm (t .underColor , int (uc &0xff )))
}
}
t .TPuts (ti .Underline )
switch us {
case UnderlineStyleDouble :
t .TPuts (t .doubleUnder )
case UnderlineStyleCurly :
t .TPuts (t .curlyUnder )
case UnderlineStyleDotted :
t .TPuts (t .dottedUnder )
case UnderlineStyleDashed :
t .TPuts (t .dashedUnder )
}
}
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 )
}
if t .enterUrl != "" && t .curstyle .url != style .url {
if style .url != "" {
t .TPuts (ti .TParm (t .enterUrl , style .url , style .urlId ))
} else {
t .TPuts (t .exitUrl )
}
}
t .curstyle = style
}
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 == "?" {
str = "? "
t .cx = -1
}
if x > t .w -width {
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 ) SetCursor (cs CursorStyle , cc Color ) {
t .Lock ()
t .cursorStyle = cs
t .cursorColor = cc
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 )
}
}
if t .cursorRGB != "" {
if t .cursorColor == ColorReset {
t .TPuts (t .cursorFg )
} else if t .cursorColor .Valid () {
r , g , b := t .cursorColor .RGB ()
t .TPuts (t .ti .TParm (t .cursorRGB , int (r ), int (g ), int (b )))
}
}
t .cx = x
t .cy = y
}
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 )
_ = t .sendFgBg (t .style .fg , t .style .bg , AttrNone )
t .TPuts (t .ti .Clear )
t .clear = false
}
func (t *tScreen ) hideCursor () {
if t .ti .HideCursor != "" {
t .TPuts (t .ti .HideCursor )
} else {
t .cx , t .cy = t .cells .Size ()
t .TPuts (t .ti .TGoto (t .cx , t .cy ))
}
}
func (t *tScreen ) draw () {
t .cx = -1
t .cy = -1
t .curstyle = styleInvalid
t .buf .Reset ()
t .buffering = true
defer func () {
t .buffering = false
}()
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 {
t .cells .SetDirty (x +1 , y , true )
}
}
x += width - 1
}
}
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 ) {
if len (t .mouse ) != 0 {
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 ) EnableFocus () {
t .Lock ()
t .focusEnabled = true
t .enableFocusReporting ()
t .Unlock ()
}
func (t *tScreen ) DisableFocus () {
t .Lock ()
t .focusEnabled = false
t .disableFocusReporting ()
t .Unlock ()
}
func (t *tScreen ) enableFocusReporting () {
if t .enableFocus != "" {
t .TPuts (t .enableFocus )
}
}
func (t *tScreen ) disableFocusReporting () {
if t .disableFocus != "" {
t .TPuts (t .disableFocus )
}
}
func (t *tScreen ) Size () (int , int ) {
t .Lock ()
w , h := t .w , t .h
t .Unlock ()
return w , h
}
func (t *tScreen ) resize () {
ws , err := t .tty .WindowSize ()
if err != nil {
return
}
if ws .Width == t .w && ws .Height == t .h {
return
}
t .cx = -1
t .cy = -1
t .cells .Resize (ws .Width , ws .Height )
t .cells .Invalidate ()
t .h = ws .Height
t .w = ws .Width
ev := &EventResize {t : time .Now (), ws : ws }
select {
case t .eventQ <- ev :
default :
}
}
func (t *tScreen ) Colors () int {
if t .truecolor {
return 1 << 24
}
return t .ti .Colors
}
func (t *tScreen ) nColors () int {
return t .ti .Colors
}
var vtACSNames = map [byte ]rune {
'+' : RuneRArrow ,
',' : RuneLArrow ,
'-' : RuneUArrow ,
'.' : RuneDArrow ,
'0' : RuneBlock ,
'`' : RuneDiamond ,
'a' : RuneCkBoard ,
'b' : '␉' ,
'c' : '␌' ,
'd' : '␋' ,
'e' : '␊' ,
'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 ,
}
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 ) 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
}
func (t *tScreen ) buildMouseEvent (x , y , btn int ) *EventMouse {
button := ButtonNone
mod := ModNone
switch btn & 0x43 {
case 0 :
button = Button1
case 1 :
button = Button3
case 2 :
button = Button2
case 3 :
button = ButtonNone
case 0x40 :
button = WheelUp
case 0x41 :
button = WheelDown
case 0x42 :
button = WheelLeft
case 0x43 :
button = WheelRight
}
if btn &0x4 != 0 {
mod |= ModShift
}
if btn &0x8 != 0 {
mod |= ModAlt
}
if btn &0x10 != 0 {
mod |= ModCtrl
}
x , y = t .clip (x , y )
return NewEventMouse (x , y , button , mod )
}
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
scroll := 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
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
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
scroll = (btn & 0x42 ) == 0x40
btn &^= 32
if b [i ] == 'm' {
btn |= 3
btn &^= 0x40
t .buttondn = false
} else if motion {
if !t .buttondn {
btn |= 3
btn &^= 0x40
}
} else if !scroll {
t .buttondn = true
}
for i >= 0 {
_, _ = buf .ReadByte ()
i --
}
*evs = append (*evs , t .buildMouseEvent (x , y , btn ))
return true , true
}
}
return true , false
}
func (t *tScreen ) parseFocus (buf *bytes .Buffer , evs *[]Event ) (bool , bool ) {
state := 0
b := buf .Bytes ()
for i := range b {
switch state {
case 0 :
if b [i ] != '\x1b' {
return false , false
}
state = 1
case 1 :
if b [i ] != '[' {
return false , false
}
state = 2
case 2 :
if b [i ] != 'I' && b [i ] != 'O' {
return false , false
}
*evs = append (*evs , NewEventFocus (b [i ] == 'I' ))
_, _ = buf .ReadByte ()
_, _ = buf .ReadByte ()
_, _ = buf .ReadByte ()
return true , true
}
}
return true , false
}
func (t *tScreen ) parseClipboard (buf *bytes .Buffer , evs *[]Event ) (bool , bool ) {
b := buf .Bytes ()
state := 0
prefix := []byte ("\x1b]52;c;" )
if len (prefix ) >= len (b ) {
if bytes .HasPrefix (prefix , b ) {
return true , false
}
return false , false
}
b = b [len (prefix ):]
for _ , c := range b {
if state == 0 {
if (c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' ) || (c >= '0' && c <= '9' ) || (c == '+' ) || (c == '/' ) || (c == '=' ) {
continue
}
if c == '\x1b' {
state = 1
continue
}
if c == '\a' {
b = b [:len (b )-1 ]
decoded := make ([]byte , base64 .StdEncoding .DecodedLen (len (b )))
if num , err := base64 .StdEncoding .Decode (decoded , b ); err == nil {
*evs = append (*evs , NewEventClipboard (decoded [:num ]))
}
_, _ = buf .ReadBytes ('\a' )
return true , true
}
return false , false
}
if state == 1 {
if c == '\\' {
b = b [:len (b )-2 ]
decoded := make ([]byte , base64 .StdEncoding .DecodedLen (len (b )))
if num , err := base64 .StdEncoding .Decode (decoded , b ); err == nil {
*evs = append (*evs , NewEventClipboard (decoded [:num ]))
}
_, _ = buf .ReadBytes ('\\' )
return true , true
}
return false , false
}
}
return true , false
}
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 ) {
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 {
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 {
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
}
}
return true , false
}
func (t *tScreen ) scanInput (buf *bytes .Buffer , expire bool ) {
evs := t .collectEventsFromInput (buf , expire )
for _ , ev := range evs {
select {
case t .eventQ <- ev :
case <- t .quit :
return
}
}
}
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 ++
}
if part , comp := t .parseFocus (buf , &res ); comp {
continue
} else if part {
partials ++
}
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 t .setClipboard != "" {
if part , comp := t .parseClipboard (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
}
by , _ := buf .ReadByte ()
mod := ModNone
if t .escaped {
t .escaped = false
mod = ModAlt
}
res = append (res , NewEventKey (KeyRune , rune (by ), mod ))
continue
}
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 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 {
select {
case t .eventQ <- NewEventError (e ):
case <- t .quit :
}
}
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
}
}
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 ()
}
func (t *tScreen ) Tty () (Tty , bool ) {
return t .tty , true
}
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 ws , err := t .tty .WindowSize (); err == nil && ws .Width != 0 && ws .Height != 0 {
t .cells .Resize (ws .Width , ws .Height )
}
stopQ := make (chan struct {})
t .stopQ = stopQ
t .enableMouse (t .mouseFlags )
t .enablePasting (t .pasteEnabled )
if t .focusEnabled {
t .enableFocusReporting ()
}
ti := t .ti
if os .Getenv ("TCELL_ALTSCREEN" ) != "disable" {
t .TPuts (ti .EnterCA )
if t .saveTitle != "" {
t .TPuts (t .saveTitle )
}
}
t .TPuts (ti .EnterKeypad )
t .TPuts (ti .HideCursor )
t .TPuts (ti .EnableAcs )
t .TPuts (ti .DisableAutoMargin )
t .TPuts (ti .Clear )
if t .title != "" && t .setTitle != "" {
t .TPuts (t .ti .TParm (t .setTitle , t .title ))
}
t .wg .Add (2 )
go t .inputLoop (stopQ )
go t .mainLoop (stopQ )
return nil
}
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 )
t .wg .Wait ()
ti := t .ti
t .cells .Resize (0 , 0 )
t .TPuts (ti .ShowCursor )
if t .cursorStyles != nil && t .cursorStyle != CursorStyleDefault {
t .TPuts (t .cursorStyles [CursorStyleDefault ])
}
if t .cursorFg != "" && t .cursorColor .Valid () {
t .TPuts (t .cursorFg )
}
t .TPuts (ti .ResetFgBg )
t .TPuts (ti .AttrOff )
t .TPuts (ti .ExitKeypad )
t .TPuts (ti .EnableAutoMargin )
if os .Getenv ("TCELL_ALTSCREEN" ) != "disable" {
if t .restoreTitle != "" {
t .TPuts (t .restoreTitle )
}
t .TPuts (ti .Clear )
t .TPuts (ti .ExitCA )
}
t .enableMouse (0 )
t .enablePasting (false )
t .disableFocusReporting ()
_ = t .tty .Stop ()
}
func (t *tScreen ) Beep () error {
t .writeString (string (byte (7 )))
return nil
}
func (t *tScreen ) finalize () {
t .disengage ()
_ = t .tty .Close ()
}
func (t *tScreen ) StopQ () <-chan struct {} {
return t .quit
}
func (t *tScreen ) EventQ () chan Event {
return t .eventQ
}
func (t *tScreen ) GetCells () *CellBuffer {
return &t .cells
}
func (t *tScreen ) SetTitle (title string ) {
t .Lock ()
t .title = title
if t .setTitle != "" && t .running {
t .TPuts (t .ti .TParm (t .setTitle , title ))
}
t .Unlock ()
}
func (t *tScreen ) SetClipboard (data []byte ) {
t .Lock ()
if t .setClipboard != "" {
encoded := base64 .StdEncoding .EncodeToString (data )
t .TPuts (t .ti .TParm (t .setClipboard , encoded ))
}
t .Unlock ()
}
func (t *tScreen ) GetClipboard () {
t .Lock ()
if t .setClipboard != "" {
t .TPuts (t .ti .TParm (t .setClipboard , "?" ))
}
t .Unlock ()
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .