aboutsummaryrefslogtreecommitdiff
path: root/vendor/maunium.net/go/tcell/tscreen.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/tcell/tscreen.go')
-rw-r--r--vendor/maunium.net/go/tcell/tscreen.go1524
1 files changed, 1524 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/tcell/tscreen.go b/vendor/maunium.net/go/tcell/tscreen.go
new file mode 100644
index 0000000..d8e62b2
--- /dev/null
+++ b/vendor/maunium.net/go/tcell/tscreen.go
@@ -0,0 +1,1524 @@
+// Copyright 2017 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 (
+ "bytes"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+ "unicode/utf8"
+
+ "golang.org/x/text/transform"
+
+ "maunium.net/go/tcell/terminfo"
+)
+
+// NewTerminfoScreen returns a Screen that uses the stock TTY interface
+// and POSIX termios, 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) {
+ ti, e := terminfo.LookupTerminfo(os.Getenv("TERM"))
+ if e != nil {
+ return nil, e
+ }
+ t := &tScreen{ti: ti}
+
+ 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.sigwinch = make(chan os.Signal, 10)
+ t.fallback = make(map[rune]string)
+ for k, v := range RuneFallbacks {
+ t.fallback[k] = v
+ }
+
+ return t, 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
+ h int
+ w int
+ fini bool
+ cells CellBuffer
+ in *os.File
+ out io.Writer
+ curstyle Style
+ style Style
+ evch chan Event
+ sigwinch chan os.Signal
+ quit chan struct{}
+ indoneq 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
+ tiosp *termiosPrivate
+ baud int
+ wasbtn bool
+ 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
+ hasSetTitle bool
+
+ sync.Mutex
+}
+
+func (t *tScreen) Init() error {
+ t.evch = make(chan Event, 10)
+ t.indoneq = make(chan struct{})
+ 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 e := t.termioInit(); e != nil {
+ return e
+ }
+
+ 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
+ }
+ if !t.truecolor {
+ t.colors = make(map[Color]Color)
+ t.palette = make([]Color, t.Colors())
+ for i := 0; i < t.Colors(); i++ {
+ t.palette[i] = Color(i)
+ // identity map for our builtin colors
+ t.colors[Color(i)] = Color(i)
+ }
+ }
+
+ t.TPuts(ti.EnterCA)
+ t.TPuts(ti.HideCursor)
+ t.TPuts(ti.EnableAcs)
+ t.TPuts(ti.Clear)
+ t.TPuts("\x1b[?2004h")
+
+ 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()
+
+ go t.mainLoop()
+ go t.inputLoop()
+
+ return nil
+}
+
+func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) {
+ if val != "" {
+ // Do not overrride 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) 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(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)
+
+ t.prepareKeyMod(KeyRight, ModAlt, ti.KeyAltRight)
+ t.prepareKeyMod(KeyLeft, ModAlt, ti.KeyAltLeft)
+ t.prepareKeyMod(KeyUp, ModAlt, ti.KeyAltUp)
+ t.prepareKeyMod(KeyDown, ModAlt, ti.KeyAltDown)
+ t.prepareKeyMod(KeyHome, ModAlt, ti.KeyAltHome)
+ t.prepareKeyMod(KeyEnd, ModAlt, ti.KeyAltEnd)
+
+ t.prepareKeyMod(KeyRight, ModAlt, ti.KeyMetaRight)
+ t.prepareKeyMod(KeyLeft, ModAlt, ti.KeyMetaLeft)
+ t.prepareKeyMod(KeyUp, ModAlt, ti.KeyMetaUp)
+ t.prepareKeyMod(KeyDown, ModAlt, ti.KeyMetaDown)
+ t.prepareKeyMod(KeyHome, ModAlt, ti.KeyMetaHome)
+ t.prepareKeyMod(KeyEnd, ModAlt, ti.KeyMetaEnd)
+
+ t.prepareKeyMod(KeyRight, ModAlt|ModShift, ti.KeyAltShfRight)
+ t.prepareKeyMod(KeyLeft, ModAlt|ModShift, ti.KeyAltShfLeft)
+ t.prepareKeyMod(KeyUp, ModAlt|ModShift, ti.KeyAltShfUp)
+ t.prepareKeyMod(KeyDown, ModAlt|ModShift, ti.KeyAltShfDown)
+ t.prepareKeyMod(KeyHome, ModAlt|ModShift, ti.KeyAltShfHome)
+ t.prepareKeyMod(KeyEnd, ModAlt|ModShift, ti.KeyAltShfEnd)
+
+ t.prepareKeyMod(KeyRight, ModAlt|ModShift, ti.KeyMetaShfRight)
+ t.prepareKeyMod(KeyLeft, ModAlt|ModShift, ti.KeyMetaShfLeft)
+ t.prepareKeyMod(KeyUp, ModAlt|ModShift, ti.KeyMetaShfUp)
+ t.prepareKeyMod(KeyDown, ModAlt|ModShift, ti.KeyMetaShfDown)
+ t.prepareKeyMod(KeyHome, ModAlt|ModShift, ti.KeyMetaShfHome)
+ t.prepareKeyMod(KeyEnd, ModAlt|ModShift, ti.KeyMetaShfEnd)
+
+ t.prepareKeyMod(KeyRight, ModCtrl|ModShift, ti.KeyCtrlShfRight)
+ t.prepareKeyMod(KeyLeft, ModCtrl|ModShift, ti.KeyCtrlShfLeft)
+ t.prepareKeyMod(KeyUp, ModCtrl|ModShift, ti.KeyCtrlShfUp)
+ t.prepareKeyMod(KeyDown, ModCtrl|ModShift, ti.KeyCtrlShfDown)
+ t.prepareKeyMod(KeyHome, ModCtrl|ModShift, ti.KeyCtrlShfHome)
+ t.prepareKeyMod(KeyEnd, ModCtrl|ModShift, ti.KeyCtrlShfEnd)
+
+ // 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")
+
+ // Extra arrow key commands
+ t.prepareKey(KeyAltUp, "\x1b[1;9A")
+ t.prepareKey(KeyAltDown, "\x1b[1;9B")
+ t.prepareKey(KeyAltLeft, "\x1b[1;9D")
+ t.prepareKey(KeyAltRight, "\x1b[1;9C")
+ t.prepareKey(KeyAltUp, "\x1b\x1b[A")
+ t.prepareKey(KeyAltDown, "\x1b\x1b[B")
+ t.prepareKey(KeyAltLeft, "\x1b\x1b[D")
+ t.prepareKey(KeyAltRight, "\x1b\x1b[C")
+ t.prepareKey(KeyAltUp, "\x1b[1;3A")
+ t.prepareKey(KeyAltDown, "\x1b[1;3B")
+ t.prepareKey(KeyAltLeft, "\x1b[1;3D")
+ t.prepareKey(KeyAltRight, "\x1b[1;3C")
+ t.prepareKey(KeyShiftUp, "\x1b[1;2A")
+ t.prepareKey(KeyShiftDown, "\x1b[1;2B")
+ t.prepareKey(KeyShiftLeft, "\x1b[1;2D")
+ t.prepareKey(KeyShiftRight, "\x1b[1;2C")
+ t.prepareKey(KeyCtrlUp, "\x1b[1;5A")
+ t.prepareKey(KeyCtrlDown, "\x1b[1;5B")
+ t.prepareKey(KeyCtrlLeft, "\x1b[1;5D")
+ t.prepareKey(KeyCtrlRight, "\x1b[1;5C")
+ t.prepareKey(KeyAltShiftUp, "\x1b[1;10A")
+ t.prepareKey(KeyAltShiftDown, "\x1b[1;10B")
+ t.prepareKey(KeyAltShiftLeft, "\x1b[1;10D")
+ t.prepareKey(KeyAltShiftRight, "\x1b[1;10C")
+ t.prepareKey(KeyAltShiftUp, "\x1b[1;4A")
+ t.prepareKey(KeyAltShiftDown, "\x1b[1;4B")
+ t.prepareKey(KeyAltShiftLeft, "\x1b[1;4D")
+ t.prepareKey(KeyAltShiftRight, "\x1b[1;4C")
+ t.prepareKey(KeyCtrlShiftUp, "\x1b[1;6A")
+ t.prepareKey(KeyCtrlShiftDown, "\x1b[1;6B")
+ t.prepareKey(KeyCtrlShiftLeft, "\x1b[1;6D")
+ t.prepareKey(KeyCtrlShiftRight, "\x1b[1;6C")
+ t.prepareKeyMod(KeyCtrlPgUp, ModCtrl, "\x1b[5;5~")
+ t.prepareKeyMod(KeyCtrlPgDn, ModCtrl, "\x1b[6;5~")
+ }
+
+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 typeable- no control sequence
+ mod = ModNone
+ }
+ t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod}
+ }
+}
+
+func (t *tScreen) ResetTitle() {
+ if t.hasSetTitle {
+ //Reset terminal title. USERNAME for Windows support. Assumes USER and USERNAME will not both be set.
+ wd, _ := os.Getwd()
+ host, _ := os.Hostname()
+ var titlestring string
+ if strings.Contains(os.Getenv("TERM"), "xterm") {
+ titlestring = "\033]2;" + os.Getenv("USER") + os.Getenv("USERNAME") + "@" + host + ": " + wd + "\007"
+ t.TPuts(titlestring)
+ }
+ if os.Getenv("TERM") == "screen" {
+ for _, s := range strings.Split(os.Getenv("SHELL"), "/") {
+ titlestring = "\033k" + s + "\033\\"
+ titlestring = "\033]2;" + os.Getenv("USER") + os.Getenv("USERNAME") + "@" + host + ": " + wd + "\007"
+ }
+ t.TPuts(titlestring)
+ }
+ }
+}
+
+func (t *tScreen) Fini() {
+ t.Lock()
+ defer t.Unlock()
+
+ ti := t.ti
+ t.cells.Resize(0, 0)
+ t.TPuts(ti.ShowCursor)
+ t.TPuts(ti.AttrOff)
+ t.ResetTitle()
+ t.TPuts(ti.Clear)
+ t.TPuts(ti.ExitCA)
+ t.TPuts(ti.ExitKeypad)
+ // Close bracketed paste
+ t.TPuts("\x1b[?2004l")
+ t.DisableMouse()
+ t.curstyle = Style(-1)
+ t.clear = false
+ t.fini = true
+
+ select {
+ case <-t.quit:
+ // do nothing, already closed
+
+ default:
+ close(t.quit)
+ }
+
+ t.termioFini()
+}
+
+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) {
+ ti := t.ti
+ if ti.Colors == 0 {
+ return
+ }
+ if t.truecolor {
+ if ti.SetFgBgRGB != "" &&
+ fg != ColorDefault && bg != ColorDefault {
+ 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)))
+ } else {
+ if fg != ColorDefault && ti.SetFgRGB != "" {
+ r, g, b := fg.RGB()
+ t.TPuts(ti.TParm(ti.SetFgRGB,
+ int(r), int(g), int(b)))
+ }
+ if bg != ColorDefault && ti.SetBgRGB != "" {
+ r, g, b := bg.RGB()
+ t.TPuts(ti.TParm(ti.SetBgRGB,
+ int(r), int(g), int(b)))
+ }
+ }
+ return
+ }
+
+ if fg != ColorDefault {
+ if v, ok := t.colors[fg]; ok {
+ fg = v
+ } else {
+ v = FindColor(fg, t.palette)
+ t.colors[fg] = v
+ fg = v
+ }
+ }
+
+ if bg != ColorDefault {
+ if v, ok := t.colors[bg]; ok {
+ bg = v
+ } else {
+ v = FindColor(bg, t.palette)
+ t.colors[bg] = v
+ bg = v
+ }
+ }
+
+ if ti.SetFgBg != "" && fg != ColorDefault && bg != ColorDefault {
+ t.TPuts(ti.TParm(ti.SetFgBg, int(fg), int(bg)))
+ } else {
+ if fg != ColorDefault && ti.SetFg != "" {
+ t.TPuts(ti.TParm(ti.SetFg, int(fg)))
+ }
+ if bg != ColorDefault && ti.SetBg != "" {
+ t.TPuts(ti.TParm(ti.SetBg, int(bg)))
+ }
+ }
+}
+
+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 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)
+
+ t.sendFgBg(fg, bg)
+ if attrs&AttrBold != 0 {
+ t.TPuts(ti.Bold)
+ }
+ if attrs&AttrItalic != 0 {
+ t.TPuts(ti.Italic)
+ }
+ if attrs&AttrStrikethrough != 0 {
+ t.TPuts(ti.Strikethrough)
+ }
+ 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)
+ }
+ 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
+ }
+
+ // XXX: check for hazeltine not being able to display ~
+
+ if x > t.w-width {
+ // too wide to fit; emit a single space instead
+ width = 1
+ str = " "
+ }
+ io.WriteString(t.out, 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) 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)
+ t.cx = x
+ t.cy = y
+}
+
+func (t *tScreen) TPuts(s string) {
+ t.ti.TPuts(t.out, s, t.baud)
+}
+
+func (t *tScreen) Show() {
+ t.Lock()
+ if !t.fini {
+ t.resize()
+ t.draw()
+ }
+ t.Unlock()
+}
+
+func (t *tScreen) clearScreen() {
+ fg, bg, _ := t.style.Decompose()
+ t.sendFgBg(fg, bg)
+ 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() {
+ // Buffer all output instead of sending it directly to the terminal
+ // We'll send it when the draw is over
+ buf := &bytes.Buffer{}
+ out := t.out
+ t.out = buf
+
+ // clobber cursor position, because we're gonna change it all
+ t.cx = -1
+ t.cy = -1
+
+ // 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
+ }
+ }
+
+ // Send everything to the terminal
+ t.out = out
+ io.WriteString(t.out, buf.String())
+
+ // restore the cursor
+ t.showCursor()
+}
+
+func (t *tScreen) EnableMouse() {
+ if len(t.mouse) != 0 {
+ t.TPuts(t.ti.TParm(t.ti.MouseMode, 1))
+ }
+}
+
+func (t *tScreen) DisableMouse() {
+ if len(t.mouse) != 0 {
+ if t.ti.MouseMode == "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" {
+ t.TPuts("\x1b[?1006l\x1b[?1015l\x1b[?10021l\x1b[?1000l")
+ } else {
+ t.TPuts(t.ti.TParm(t.ti.MouseMode, 0))
+ }
+ }
+}
+
+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.getWinSize(); 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
+}
+
+func (t *tScreen) PollEvent() Event {
+ select {
+ case <-t.quit:
+ return nil
+ case ev := <-t.evch:
+ return ev
+ }
+}
+
+// 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.
+// Its 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
+}
+
+func (t *tScreen) postMouseEvent(x, y, btn int, motion bool) {
+
+ // 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
+ t.wasbtn = true
+ case 1:
+ button = Button2
+ t.wasbtn = true
+ case 2:
+ button = Button3
+ t.wasbtn = true
+ case 3:
+ button = ButtonNone
+ t.wasbtn = false
+ case 0x40:
+ if !t.wasbtn {
+ button = WheelUp
+ } else {
+ button = Button1
+ }
+ case 0x41:
+ if !t.wasbtn {
+ button = WheelDown
+ } else {
+ button = Button2
+ }
+ }
+
+ 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)
+
+ ev := NewEventMouse(x, y, button, mod, motion)
+ t.PostEvent(ev)
+}
+
+// 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) (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--
+ }
+ t.postMouseEvent(x, y, btn, motion)
+ return true, true
+ }
+ }
+
+ // incomplete & inconclusve 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) (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])
+ if btn != 128 && btn != 129 {
+ btn = int(b[i])
+ } else {
+ btn = 99
+ }
+ state++
+ case 4:
+ x = int(b[i]) - 32 - 1
+ state++
+ case 5:
+ y = int(b[i]) - 32 - 1
+ for i >= 0 {
+ buf.ReadByte()
+ i--
+ }
+ t.postMouseEvent(x, y, btn, false)
+ return true, true
+ }
+ }
+ return true, false
+}
+
+func (t *tScreen) parseFunctionKey(buf *bytes.Buffer) (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
+ }
+ ev := NewEventKey(k.key, r, mod)
+ t.PostEvent(ev)
+ 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) (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
+ }
+ ev := NewEventKey(KeyRune, rune(b[0]), mod)
+ t.PostEvent(ev)
+ buf.ReadByte()
+ return true, true
+ }
+
+ if b[0] < 0x80 {
+ // Low numbered values are control keys, not runes.
+ return false, false
+ }
+
+ utfb := make([]byte, 12)
+ for l := 1; l <= len(b); l++ {
+ t.decoder.Reset()
+ nout, nin, e := t.decoder.Transform(utfb, b[:l], true)
+ if e == transform.ErrShortSrc {
+ continue
+ }
+ if nout != 0 {
+ r, _ := utf8.DecodeRune(utfb[:nout])
+ if r != utf8.RuneError {
+ mod := ModNone
+ if t.escaped {
+ mod = ModAlt
+ t.escaped = false
+ }
+ ev := NewEventKey(KeyRune, r, mod)
+ t.PostEvent(ev)
+ }
+ for nin > 0 {
+ buf.ReadByte()
+ nin--
+ }
+ return true, true
+ }
+ }
+ // Looks like potential escape
+ return true, false
+}
+
+func (t *tScreen) parseBracketedPaste(buf *bytes.Buffer) (bool, bool) {
+ b := buf.Bytes()
+
+ // Replace all carriage returns with newlines
+ str := strings.Replace(string(b), "\r", "\n", -1)
+ if strings.HasPrefix(str, "\x1b[200~") {
+ // The bracketed paste has started
+ if strings.HasSuffix(str, "\x1b[201~") {
+ // The bracketed paste has ended
+ // Strip out the start and end sequences
+ ev := NewEventPaste(str[6 : len(b)-6])
+ t.PostEvent(ev)
+ for i := 0; i < len(b); i++ {
+ buf.ReadByte()
+ }
+ return true, true
+ }
+ // There is still more coming
+ return true, false
+ }
+ return false, false
+}
+
+func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
+
+ t.Lock()
+ defer t.Unlock()
+
+ for {
+ b := buf.Bytes()
+ if len(b) == 0 {
+ buf.Reset()
+ return
+ }
+ if !bytes.Contains(b, []byte("\x1b")) && utf8.RuneCount(b) > 1 {
+ ev := &EventPaste{t: time.Now(), text: string(bytes.Replace(b, []byte("\r"), []byte("\n"), -1))}
+ t.PostEvent(ev)
+ for i := 0; i < len(b); i++ {
+ buf.ReadByte()
+ }
+ continue
+ }
+
+ partials := 0
+
+ if part, comp := t.parseBracketedPaste(buf); comp {
+ continue
+ } else if part {
+ partials++
+ }
+
+ if part, comp := t.parseRune(buf); comp {
+ continue
+ } else if part {
+ partials++
+ }
+
+ if part, comp := t.parseFunctionKey(buf); 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); comp {
+ continue
+ } else if part {
+ partials++
+ }
+
+ if part, comp := t.parseSgrMouse(buf); comp {
+ continue
+ } else if part {
+ partials++
+ }
+ }
+
+ if partials == 0 || expire {
+ // 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.
+ if b[0] == '\x1b' {
+ if len(b) == 1 {
+ ev := NewEventKey(KeyEsc, 0, ModNone)
+ t.PostEvent(ev)
+ t.escaped = false
+ } else {
+ t.escaped = true
+ }
+ buf.ReadByte()
+ continue
+ }
+
+ by, _ := buf.ReadByte()
+ mod := ModNone
+ if t.escaped {
+ t.escaped = false
+ mod = ModAlt
+ }
+ ev := NewEventKey(KeyRune, rune(by), mod)
+ t.PostEvent(ev)
+ continue
+ }
+
+ // well we have some partial data, wait until we get
+ // some more
+ break
+ }
+}
+
+func (t *tScreen) mainLoop() {
+ buf := &bytes.Buffer{}
+ for {
+ select {
+ case <-t.quit:
+ close(t.indoneq)
+ return
+ case <-t.sigwinch:
+ 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 it's
+ // 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() {
+
+ for {
+ chunk := make([]byte, 128)
+ n, e := t.in.Read(chunk)
+ switch e {
+ case io.EOF:
+ case nil:
+ default:
+ t.PostEvent(NewEventError(e))
+ return
+ }
+ 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) Resize(int, int, int, int) {}
+
+func (t *tScreen) SetTitle(title string) {
+ if strings.Compare(os.Getenv("TERM"), "screen") == 0 {
+ t.hasSetTitle = true
+ t.TPuts("\033k" + title + "\033\\")
+ }
+ if strings.Contains(os.Getenv("TERM"), "xterm") {
+ t.TPuts("\033]2;" + title + "\007")
+ t.hasSetTitle = true
+ }
+}