nt

A sensible note-taking program
git clone git://git.laack.co/nt.git
Log | Files | Refs | README

tscreen.go (52591B)


      1 // Copyright 2024 The TCell Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use file except in compliance with the License.
      5 // You may obtain a copy of the license at
      6 //
      7 //    http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 //go:build !(js && wasm)
     16 // +build !js !wasm
     17 
     18 package tcell
     19 
     20 import (
     21 	"bytes"
     22 	"encoding/base64"
     23 	"errors"
     24 	"io"
     25 	"os"
     26 	"strconv"
     27 	"strings"
     28 	"sync"
     29 	"time"
     30 	"unicode/utf8"
     31 
     32 	"golang.org/x/term"
     33 	"golang.org/x/text/transform"
     34 
     35 	"github.com/gdamore/tcell/v2/terminfo"
     36 )
     37 
     38 // NewTerminfoScreen returns a Screen that uses the stock TTY interface
     39 // and POSIX terminal control, combined with a terminfo description taken from
     40 // the $TERM environment variable.  It returns an error if the terminal
     41 // is not supported for any reason.
     42 //
     43 // For terminals that do not support dynamic resize events, the $LINES
     44 // $COLUMNS environment variables can be set to the actual window size,
     45 // otherwise defaults taken from the terminal database are used.
     46 func NewTerminfoScreen() (Screen, error) {
     47 	return NewTerminfoScreenFromTty(nil)
     48 }
     49 
     50 // LookupTerminfo attempts to find a definition for the named $TERM falling
     51 // back to attempting to parse the output from infocmp.
     52 func LookupTerminfo(name string) (ti *terminfo.Terminfo, e error) {
     53 	ti, e = terminfo.LookupTerminfo(name)
     54 	if e != nil {
     55 		ti, e = loadDynamicTerminfo(name)
     56 		if e != nil {
     57 			return nil, e
     58 		}
     59 		terminfo.AddTerminfo(ti)
     60 	}
     61 
     62 	return
     63 }
     64 
     65 // NewTerminfoScreenFromTtyTerminfo returns a Screen using a custom Tty
     66 // implementation  and custom terminfo specification.
     67 // If the passed in tty is nil, then a reasonable default (typically /dev/tty)
     68 // is presumed, at least on UNIX hosts. (Windows hosts will typically fail this
     69 // call altogether.)
     70 // If passed terminfo is nil, then TERM environment variable is queried for
     71 // terminal specification.
     72 func NewTerminfoScreenFromTtyTerminfo(tty Tty, ti *terminfo.Terminfo) (s Screen, e error) {
     73 	if ti == nil {
     74 		ti, e = LookupTerminfo(os.Getenv("TERM"))
     75 		if e != nil {
     76 			return
     77 		}
     78 	}
     79 
     80 	t := &tScreen{ti: ti, tty: tty}
     81 
     82 	t.keyexist = make(map[Key]bool)
     83 	t.keycodes = make(map[string]*tKeyCode)
     84 	if len(ti.Mouse) > 0 {
     85 		t.mouse = []byte(ti.Mouse)
     86 	}
     87 	t.prepareKeys()
     88 	t.buildAcsMap()
     89 	t.resizeQ = make(chan bool, 1)
     90 	t.fallback = make(map[rune]string)
     91 	for k, v := range RuneFallbacks {
     92 		t.fallback[k] = v
     93 	}
     94 
     95 	return &baseScreen{screenImpl: t}, nil
     96 }
     97 
     98 // NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation.
     99 // If the passed in tty is nil, then a reasonable default (typically /dev/tty)
    100 // is presumed, at least on UNIX hosts. (Windows hosts will typically fail this
    101 // call altogether.)
    102 func NewTerminfoScreenFromTty(tty Tty) (Screen, error) {
    103 	return NewTerminfoScreenFromTtyTerminfo(tty, nil)
    104 }
    105 
    106 // tKeyCode represents a combination of a key code and modifiers.
    107 type tKeyCode struct {
    108 	key Key
    109 	mod ModMask
    110 }
    111 
    112 // tScreen represents a screen backed by a terminfo implementation.
    113 type tScreen struct {
    114 	ti           *terminfo.Terminfo
    115 	tty          Tty
    116 	h            int
    117 	w            int
    118 	fini         bool
    119 	cells        CellBuffer
    120 	buffering    bool // true if we are collecting writes to buf instead of sending directly to out
    121 	buf          bytes.Buffer
    122 	curstyle     Style
    123 	style        Style
    124 	resizeQ      chan bool
    125 	quit         chan struct{}
    126 	keyexist     map[Key]bool
    127 	keycodes     map[string]*tKeyCode
    128 	keychan      chan []byte
    129 	keytimer     *time.Timer
    130 	keyexpire    time.Time
    131 	cx           int
    132 	cy           int
    133 	mouse        []byte
    134 	clear        bool
    135 	cursorx      int
    136 	cursory      int
    137 	acs          map[rune]string
    138 	charset      string
    139 	encoder      transform.Transformer
    140 	decoder      transform.Transformer
    141 	fallback     map[rune]string
    142 	colors       map[Color]Color
    143 	palette      []Color
    144 	truecolor    bool
    145 	escaped      bool
    146 	buttondn     bool
    147 	finiOnce     sync.Once
    148 	enablePaste  string
    149 	disablePaste string
    150 	enterUrl     string
    151 	exitUrl      string
    152 	setWinSize   string
    153 	enableFocus  string
    154 	disableFocus string
    155 	doubleUnder  string
    156 	curlyUnder   string
    157 	dottedUnder  string
    158 	dashedUnder  string
    159 	underColor   string
    160 	underRGB     string
    161 	underFg      string
    162 	cursorStyles map[CursorStyle]string
    163 	cursorStyle  CursorStyle
    164 	cursorColor  Color
    165 	cursorRGB    string
    166 	cursorFg     string
    167 	saved        *term.State
    168 	stopQ        chan struct{}
    169 	eventQ       chan Event
    170 	running      bool
    171 	wg           sync.WaitGroup
    172 	mouseFlags   MouseFlags
    173 	pasteEnabled bool
    174 	focusEnabled bool
    175 	setTitle     string
    176 	saveTitle    string
    177 	restoreTitle string
    178 	title        string
    179 	setClipboard string
    180 
    181 	sync.Mutex
    182 }
    183 
    184 func (t *tScreen) Init() error {
    185 	if e := t.initialize(); e != nil {
    186 		return e
    187 	}
    188 
    189 	t.keychan = make(chan []byte, 10)
    190 	t.keytimer = time.NewTimer(time.Millisecond * 50)
    191 	t.charset = "UTF-8"
    192 
    193 	t.charset = getCharset()
    194 	if enc := GetEncoding(t.charset); enc != nil {
    195 		t.encoder = enc.NewEncoder()
    196 		t.decoder = enc.NewDecoder()
    197 	} else {
    198 		return ErrNoCharset
    199 	}
    200 	ti := t.ti
    201 
    202 	// environment overrides
    203 	w := ti.Columns
    204 	h := ti.Lines
    205 	if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 {
    206 		h = i
    207 	}
    208 	if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 {
    209 		w = i
    210 	}
    211 	if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" {
    212 		t.truecolor = true
    213 	}
    214 	// A user who wants to have his themes honored can
    215 	// set this environment variable.
    216 	if os.Getenv("TCELL_TRUECOLOR") == "disable" {
    217 		t.truecolor = false
    218 	}
    219 	nColors := t.nColors()
    220 	if nColors > 256 {
    221 		nColors = 256 // clip to reasonable limits
    222 	}
    223 	t.colors = make(map[Color]Color, nColors)
    224 	t.palette = make([]Color, nColors)
    225 	for i := 0; i < nColors; i++ {
    226 		t.palette[i] = Color(i) | ColorValid
    227 		// identity map for our builtin colors
    228 		t.colors[Color(i)|ColorValid] = Color(i) | ColorValid
    229 	}
    230 
    231 	t.quit = make(chan struct{})
    232 	t.eventQ = make(chan Event, 10)
    233 
    234 	t.Lock()
    235 	t.cx = -1
    236 	t.cy = -1
    237 	t.style = StyleDefault
    238 	t.cells.Resize(w, h)
    239 	t.cursorx = -1
    240 	t.cursory = -1
    241 	t.resize()
    242 	t.Unlock()
    243 
    244 	if err := t.engage(); err != nil {
    245 		return err
    246 	}
    247 
    248 	return nil
    249 }
    250 
    251 func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) {
    252 	if val != "" {
    253 		// Do not override codes that already exist
    254 		if _, exist := t.keycodes[val]; !exist {
    255 			t.keyexist[key] = true
    256 			t.keycodes[val] = &tKeyCode{key: key, mod: mod}
    257 		}
    258 	}
    259 }
    260 
    261 func (t *tScreen) prepareKeyModReplace(key Key, replace Key, mod ModMask, val string) {
    262 	if val != "" {
    263 		// Do not override codes that already exist
    264 		if old, exist := t.keycodes[val]; !exist || old.key == replace {
    265 			t.keyexist[key] = true
    266 			t.keycodes[val] = &tKeyCode{key: key, mod: mod}
    267 		}
    268 	}
    269 }
    270 
    271 func (t *tScreen) prepareKeyModXTerm(key Key, val string) {
    272 
    273 	if strings.HasPrefix(val, "\x1b[") && strings.HasSuffix(val, "~") {
    274 
    275 		// Drop the trailing ~
    276 		val = val[:len(val)-1]
    277 
    278 		// These suffixes are calculated assuming Xterm style modifier suffixes.
    279 		// Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for
    280 		// more information (specifically "PC-Style Function Keys").
    281 		t.prepareKeyModReplace(key, key+12, ModShift, val+";2~")
    282 		t.prepareKeyModReplace(key, key+48, ModAlt, val+";3~")
    283 		t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, val+";4~")
    284 		t.prepareKeyModReplace(key, key+24, ModCtrl, val+";5~")
    285 		t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, val+";6~")
    286 		t.prepareKeyMod(key, ModAlt|ModCtrl, val+";7~")
    287 		t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, val+";8~")
    288 		t.prepareKeyMod(key, ModMeta, val+";9~")
    289 		t.prepareKeyMod(key, ModMeta|ModShift, val+";10~")
    290 		t.prepareKeyMod(key, ModMeta|ModAlt, val+";11~")
    291 		t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, val+";12~")
    292 		t.prepareKeyMod(key, ModMeta|ModCtrl, val+";13~")
    293 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, val+";14~")
    294 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, val+";15~")
    295 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, val+";16~")
    296 	} else if strings.HasPrefix(val, "\x1bO") && len(val) == 3 {
    297 		val = val[2:]
    298 		t.prepareKeyModReplace(key, key+12, ModShift, "\x1b[1;2"+val)
    299 		t.prepareKeyModReplace(key, key+48, ModAlt, "\x1b[1;3"+val)
    300 		t.prepareKeyModReplace(key, key+24, ModCtrl, "\x1b[1;5"+val)
    301 		t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, "\x1b[1;6"+val)
    302 		t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, "\x1b[1;4"+val)
    303 		t.prepareKeyMod(key, ModAlt|ModCtrl, "\x1b[1;7"+val)
    304 		t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, "\x1b[1;8"+val)
    305 		t.prepareKeyMod(key, ModMeta, "\x1b[1;9"+val)
    306 		t.prepareKeyMod(key, ModMeta|ModShift, "\x1b[1;10"+val)
    307 		t.prepareKeyMod(key, ModMeta|ModAlt, "\x1b[1;11"+val)
    308 		t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, "\x1b[1;12"+val)
    309 		t.prepareKeyMod(key, ModMeta|ModCtrl, "\x1b[1;13"+val)
    310 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, "\x1b[1;14"+val)
    311 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, "\x1b[1;15"+val)
    312 		t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, "\x1b[1;16"+val)
    313 	}
    314 }
    315 
    316 func (t *tScreen) prepareXtermModifiers() {
    317 	if t.ti.Modifiers != terminfo.ModifiersXTerm {
    318 		return
    319 	}
    320 	t.prepareKeyModXTerm(KeyRight, t.ti.KeyRight)
    321 	t.prepareKeyModXTerm(KeyLeft, t.ti.KeyLeft)
    322 	t.prepareKeyModXTerm(KeyUp, t.ti.KeyUp)
    323 	t.prepareKeyModXTerm(KeyDown, t.ti.KeyDown)
    324 	t.prepareKeyModXTerm(KeyInsert, t.ti.KeyInsert)
    325 	t.prepareKeyModXTerm(KeyDelete, t.ti.KeyDelete)
    326 	t.prepareKeyModXTerm(KeyPgUp, t.ti.KeyPgUp)
    327 	t.prepareKeyModXTerm(KeyPgDn, t.ti.KeyPgDn)
    328 	t.prepareKeyModXTerm(KeyHome, t.ti.KeyHome)
    329 	t.prepareKeyModXTerm(KeyEnd, t.ti.KeyEnd)
    330 	t.prepareKeyModXTerm(KeyF1, t.ti.KeyF1)
    331 	t.prepareKeyModXTerm(KeyF2, t.ti.KeyF2)
    332 	t.prepareKeyModXTerm(KeyF3, t.ti.KeyF3)
    333 	t.prepareKeyModXTerm(KeyF4, t.ti.KeyF4)
    334 	t.prepareKeyModXTerm(KeyF5, t.ti.KeyF5)
    335 	t.prepareKeyModXTerm(KeyF6, t.ti.KeyF6)
    336 	t.prepareKeyModXTerm(KeyF7, t.ti.KeyF7)
    337 	t.prepareKeyModXTerm(KeyF8, t.ti.KeyF8)
    338 	t.prepareKeyModXTerm(KeyF9, t.ti.KeyF9)
    339 	t.prepareKeyModXTerm(KeyF10, t.ti.KeyF10)
    340 	t.prepareKeyModXTerm(KeyF11, t.ti.KeyF11)
    341 	t.prepareKeyModXTerm(KeyF12, t.ti.KeyF12)
    342 }
    343 
    344 func (t *tScreen) prepareBracketedPaste() {
    345 	// Another workaround for lack of reporting in terminfo.
    346 	// We assume if the terminal has a mouse entry, that it
    347 	// offers bracketed paste.  But we allow specific overrides
    348 	// via our terminal database.
    349 	if t.ti.EnablePaste != "" {
    350 		t.enablePaste = t.ti.EnablePaste
    351 		t.disablePaste = t.ti.DisablePaste
    352 		t.prepareKey(keyPasteStart, t.ti.PasteStart)
    353 		t.prepareKey(keyPasteEnd, t.ti.PasteEnd)
    354 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    355 		t.enablePaste = "\x1b[?2004h"
    356 		t.disablePaste = "\x1b[?2004l"
    357 		t.prepareKey(keyPasteStart, "\x1b[200~")
    358 		t.prepareKey(keyPasteEnd, "\x1b[201~")
    359 	}
    360 }
    361 
    362 func (t *tScreen) prepareUnderlines() {
    363 	if t.ti.DoubleUnderline != "" {
    364 		t.doubleUnder = t.ti.DoubleUnderline
    365 	} else if t.ti.XTermLike {
    366 		t.doubleUnder = "\x1b[4:2m"
    367 	}
    368 	if t.ti.CurlyUnderline != "" {
    369 		t.curlyUnder = t.ti.CurlyUnderline
    370 	} else if t.ti.XTermLike {
    371 		t.curlyUnder = "\x1b[4:3m"
    372 	}
    373 	if t.ti.DottedUnderline != "" {
    374 		t.dottedUnder = t.ti.DottedUnderline
    375 	} else if t.ti.XTermLike {
    376 		t.dottedUnder = "\x1b[4:4m"
    377 	}
    378 	if t.ti.DashedUnderline != "" {
    379 		t.dashedUnder = t.ti.DashedUnderline
    380 	} else if t.ti.XTermLike {
    381 		t.dashedUnder = "\x1b[4:5m"
    382 	}
    383 
    384 	// Underline colors.  We're not going to rely upon terminfo for this
    385 	// Essentially all terminals that support the curly underlines are
    386 	// expected to also support coloring them too - which reflects actual
    387 	// practice since these were introduced at about the same time.
    388 	if t.ti.UnderlineColor != "" {
    389 		t.underColor = t.ti.UnderlineColor
    390 	} else if t.curlyUnder != "" {
    391 		t.underColor = "\x1b[58:5:%p1%dm"
    392 	}
    393 	if t.ti.UnderlineColorRGB != "" {
    394 		// An interesting wart here is that in order to facilitate
    395 		// using just a single parameter, the Setulc parameter takes
    396 		// the 24-bit color as an integer rather than separate bytes.
    397 		// This matches the "new" style direct color approach that
    398 		// ncurses took, even though everyone else went another way.
    399 		t.underRGB = t.ti.UnderlineColorRGB
    400 	} else if t.underColor != "" {
    401 		t.underRGB = "\x1b[58:2::%p1%d:%p2%d:%p3%dm"
    402 	}
    403 	if t.ti.UnderlineColorReset != "" {
    404 		t.underFg = t.ti.UnderlineColorReset
    405 	} else if t.curlyUnder != "" {
    406 		t.underFg = "\x1b[59m"
    407 	}
    408 }
    409 
    410 func (t *tScreen) prepareExtendedOSC() {
    411 	// Linux is a special beast - because it has a mouse entry, but does
    412 	// not swallow these OSC commands properly.
    413 	if strings.Contains(t.ti.Name, "linux") {
    414 		return
    415 	}
    416 	// More stuff for limits in terminfo.  This time we are applying
    417 	// the most common OSC (operating system commands).  Generally
    418 	// terminals that don't understand these will ignore them.
    419 	// Again, we condition this based on mouse capabilities.
    420 	if t.ti.EnterUrl != "" {
    421 		t.enterUrl = t.ti.EnterUrl
    422 		t.exitUrl = t.ti.ExitUrl
    423 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    424 		t.enterUrl = "\x1b]8;%p2%s;%p1%s\x1b\\"
    425 		t.exitUrl = "\x1b]8;;\x1b\\"
    426 	}
    427 
    428 	if t.ti.SetWindowSize != "" {
    429 		t.setWinSize = t.ti.SetWindowSize
    430 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    431 		t.setWinSize = "\x1b[8;%p1%p2%d;%dt"
    432 	}
    433 
    434 	if t.ti.EnableFocusReporting != "" {
    435 		t.enableFocus = t.ti.EnableFocusReporting
    436 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    437 		t.enableFocus = "\x1b[?1004h"
    438 	}
    439 	if t.ti.DisableFocusReporting != "" {
    440 		t.disableFocus = t.ti.DisableFocusReporting
    441 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    442 		t.disableFocus = "\x1b[?1004l"
    443 	}
    444 
    445 	if t.ti.SetWindowTitle != "" {
    446 		t.setTitle = t.ti.SetWindowTitle
    447 	} else if t.ti.XTermLike {
    448 		t.saveTitle = "\x1b[22;2t"
    449 		t.restoreTitle = "\x1b[23;2t"
    450 		// this also tries to request that UTF-8 is allowed in the title
    451 		t.setTitle = "\x1b[>2t\x1b]2;%p1%s\x1b\\"
    452 	}
    453 
    454 	if t.setClipboard == "" && t.ti.XTermLike {
    455 		// this string takes a base64 string and sends it to the clipboard.
    456 		// it will also be able to retrieve the clipboard using "?" as the
    457 		// sent string, when we support that.
    458 		t.setClipboard = "\x1b]52;c;%p1%s\x1b\\"
    459 	}
    460 }
    461 
    462 func (t *tScreen) prepareCursorStyles() {
    463 	// Another workaround for lack of reporting in terminfo.
    464 	// We assume if the terminal has a mouse entry, that it
    465 	// offers bracketed paste.  But we allow specific overrides
    466 	// via our terminal database.
    467 	if t.ti.CursorDefault != "" {
    468 		t.cursorStyles = map[CursorStyle]string{
    469 			CursorStyleDefault:           t.ti.CursorDefault,
    470 			CursorStyleBlinkingBlock:     t.ti.CursorBlinkingBlock,
    471 			CursorStyleSteadyBlock:       t.ti.CursorSteadyBlock,
    472 			CursorStyleBlinkingUnderline: t.ti.CursorBlinkingUnderline,
    473 			CursorStyleSteadyUnderline:   t.ti.CursorSteadyUnderline,
    474 			CursorStyleBlinkingBar:       t.ti.CursorBlinkingBar,
    475 			CursorStyleSteadyBar:         t.ti.CursorSteadyBar,
    476 		}
    477 	} else if t.ti.Mouse != "" || t.ti.XTermLike {
    478 		t.cursorStyles = map[CursorStyle]string{
    479 			CursorStyleDefault:           "\x1b[0 q",
    480 			CursorStyleBlinkingBlock:     "\x1b[1 q",
    481 			CursorStyleSteadyBlock:       "\x1b[2 q",
    482 			CursorStyleBlinkingUnderline: "\x1b[3 q",
    483 			CursorStyleSteadyUnderline:   "\x1b[4 q",
    484 			CursorStyleBlinkingBar:       "\x1b[5 q",
    485 			CursorStyleSteadyBar:         "\x1b[6 q",
    486 		}
    487 	}
    488 	if t.ti.CursorColorRGB != "" {
    489 		// if it was X11 style with just a single %p1%s, then convert
    490 		t.cursorRGB = t.ti.CursorColorRGB
    491 	}
    492 	if t.ti.CursorColorReset != "" {
    493 		t.cursorFg = t.ti.CursorColorReset
    494 	}
    495 	if t.cursorRGB == "" {
    496 		t.cursorRGB = "\x1b]12;%p1%s\007"
    497 		t.cursorFg = "\x1b]112\007"
    498 	}
    499 
    500 	// convert XTERM style color names to RGB color code.  We have no way to do palette colors
    501 	t.cursorRGB = strings.Replace(t.cursorRGB, "%p1%s", "#%p1%02x%p2%02x%p3%02x", 1)
    502 }
    503 
    504 func (t *tScreen) prepareKey(key Key, val string) {
    505 	t.prepareKeyMod(key, ModNone, val)
    506 }
    507 
    508 func (t *tScreen) prepareKeys() {
    509 	ti := t.ti
    510 	if strings.HasPrefix(ti.Name, "xterm") {
    511 		// assume its some form of XTerm clone
    512 		t.ti.XTermLike = true
    513 		ti.XTermLike = true
    514 	}
    515 	t.prepareKey(KeyBackspace, ti.KeyBackspace)
    516 	t.prepareKey(KeyF1, ti.KeyF1)
    517 	t.prepareKey(KeyF2, ti.KeyF2)
    518 	t.prepareKey(KeyF3, ti.KeyF3)
    519 	t.prepareKey(KeyF4, ti.KeyF4)
    520 	t.prepareKey(KeyF5, ti.KeyF5)
    521 	t.prepareKey(KeyF6, ti.KeyF6)
    522 	t.prepareKey(KeyF7, ti.KeyF7)
    523 	t.prepareKey(KeyF8, ti.KeyF8)
    524 	t.prepareKey(KeyF9, ti.KeyF9)
    525 	t.prepareKey(KeyF10, ti.KeyF10)
    526 	t.prepareKey(KeyF11, ti.KeyF11)
    527 	t.prepareKey(KeyF12, ti.KeyF12)
    528 	t.prepareKey(KeyF13, ti.KeyF13)
    529 	t.prepareKey(KeyF14, ti.KeyF14)
    530 	t.prepareKey(KeyF15, ti.KeyF15)
    531 	t.prepareKey(KeyF16, ti.KeyF16)
    532 	t.prepareKey(KeyF17, ti.KeyF17)
    533 	t.prepareKey(KeyF18, ti.KeyF18)
    534 	t.prepareKey(KeyF19, ti.KeyF19)
    535 	t.prepareKey(KeyF20, ti.KeyF20)
    536 	t.prepareKey(KeyF21, ti.KeyF21)
    537 	t.prepareKey(KeyF22, ti.KeyF22)
    538 	t.prepareKey(KeyF23, ti.KeyF23)
    539 	t.prepareKey(KeyF24, ti.KeyF24)
    540 	t.prepareKey(KeyF25, ti.KeyF25)
    541 	t.prepareKey(KeyF26, ti.KeyF26)
    542 	t.prepareKey(KeyF27, ti.KeyF27)
    543 	t.prepareKey(KeyF28, ti.KeyF28)
    544 	t.prepareKey(KeyF29, ti.KeyF29)
    545 	t.prepareKey(KeyF30, ti.KeyF30)
    546 	t.prepareKey(KeyF31, ti.KeyF31)
    547 	t.prepareKey(KeyF32, ti.KeyF32)
    548 	t.prepareKey(KeyF33, ti.KeyF33)
    549 	t.prepareKey(KeyF34, ti.KeyF34)
    550 	t.prepareKey(KeyF35, ti.KeyF35)
    551 	t.prepareKey(KeyF36, ti.KeyF36)
    552 	t.prepareKey(KeyF37, ti.KeyF37)
    553 	t.prepareKey(KeyF38, ti.KeyF38)
    554 	t.prepareKey(KeyF39, ti.KeyF39)
    555 	t.prepareKey(KeyF40, ti.KeyF40)
    556 	t.prepareKey(KeyF41, ti.KeyF41)
    557 	t.prepareKey(KeyF42, ti.KeyF42)
    558 	t.prepareKey(KeyF43, ti.KeyF43)
    559 	t.prepareKey(KeyF44, ti.KeyF44)
    560 	t.prepareKey(KeyF45, ti.KeyF45)
    561 	t.prepareKey(KeyF46, ti.KeyF46)
    562 	t.prepareKey(KeyF47, ti.KeyF47)
    563 	t.prepareKey(KeyF48, ti.KeyF48)
    564 	t.prepareKey(KeyF49, ti.KeyF49)
    565 	t.prepareKey(KeyF50, ti.KeyF50)
    566 	t.prepareKey(KeyF51, ti.KeyF51)
    567 	t.prepareKey(KeyF52, ti.KeyF52)
    568 	t.prepareKey(KeyF53, ti.KeyF53)
    569 	t.prepareKey(KeyF54, ti.KeyF54)
    570 	t.prepareKey(KeyF55, ti.KeyF55)
    571 	t.prepareKey(KeyF56, ti.KeyF56)
    572 	t.prepareKey(KeyF57, ti.KeyF57)
    573 	t.prepareKey(KeyF58, ti.KeyF58)
    574 	t.prepareKey(KeyF59, ti.KeyF59)
    575 	t.prepareKey(KeyF60, ti.KeyF60)
    576 	t.prepareKey(KeyF61, ti.KeyF61)
    577 	t.prepareKey(KeyF62, ti.KeyF62)
    578 	t.prepareKey(KeyF63, ti.KeyF63)
    579 	t.prepareKey(KeyF64, ti.KeyF64)
    580 	t.prepareKey(KeyInsert, ti.KeyInsert)
    581 	t.prepareKey(KeyDelete, ti.KeyDelete)
    582 	t.prepareKey(KeyHome, ti.KeyHome)
    583 	t.prepareKey(KeyEnd, ti.KeyEnd)
    584 	t.prepareKey(KeyUp, ti.KeyUp)
    585 	t.prepareKey(KeyDown, ti.KeyDown)
    586 	t.prepareKey(KeyLeft, ti.KeyLeft)
    587 	t.prepareKey(KeyRight, ti.KeyRight)
    588 	t.prepareKey(KeyPgUp, ti.KeyPgUp)
    589 	t.prepareKey(KeyPgDn, ti.KeyPgDn)
    590 	t.prepareKey(KeyHelp, ti.KeyHelp)
    591 	t.prepareKey(KeyPrint, ti.KeyPrint)
    592 	t.prepareKey(KeyCancel, ti.KeyCancel)
    593 	t.prepareKey(KeyExit, ti.KeyExit)
    594 	t.prepareKey(KeyBacktab, ti.KeyBacktab)
    595 
    596 	t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight)
    597 	t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft)
    598 	t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp)
    599 	t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown)
    600 	t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome)
    601 	t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd)
    602 	t.prepareKeyMod(KeyPgUp, ModShift, ti.KeyShfPgUp)
    603 	t.prepareKeyMod(KeyPgDn, ModShift, ti.KeyShfPgDn)
    604 
    605 	t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight)
    606 	t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft)
    607 	t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp)
    608 	t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown)
    609 	t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome)
    610 	t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd)
    611 
    612 	// Sadly, xterm handling of keycodes is somewhat erratic.  In
    613 	// particular, different codes are sent depending on application
    614 	// mode is in use or not, and the entries for many of these are
    615 	// simply absent from terminfo on many systems.  So we insert
    616 	// a number of escape sequences if they are not already used, in
    617 	// order to have the widest correct usage.  Note that prepareKey
    618 	// will not inject codes if the escape sequence is already known.
    619 	// We also only do this for terminals that have the application
    620 	// mode present.
    621 
    622 	// Cursor mode
    623 	if ti.EnterKeypad != "" {
    624 		t.prepareKey(KeyUp, "\x1b[A")
    625 		t.prepareKey(KeyDown, "\x1b[B")
    626 		t.prepareKey(KeyRight, "\x1b[C")
    627 		t.prepareKey(KeyLeft, "\x1b[D")
    628 		t.prepareKey(KeyEnd, "\x1b[F")
    629 		t.prepareKey(KeyHome, "\x1b[H")
    630 		t.prepareKey(KeyDelete, "\x1b[3~")
    631 		t.prepareKey(KeyHome, "\x1b[1~")
    632 		t.prepareKey(KeyEnd, "\x1b[4~")
    633 		t.prepareKey(KeyPgUp, "\x1b[5~")
    634 		t.prepareKey(KeyPgDn, "\x1b[6~")
    635 
    636 		// Application mode
    637 		t.prepareKey(KeyUp, "\x1bOA")
    638 		t.prepareKey(KeyDown, "\x1bOB")
    639 		t.prepareKey(KeyRight, "\x1bOC")
    640 		t.prepareKey(KeyLeft, "\x1bOD")
    641 		t.prepareKey(KeyHome, "\x1bOH")
    642 	}
    643 
    644 	t.prepareKey(keyPasteStart, ti.PasteStart)
    645 	t.prepareKey(keyPasteEnd, ti.PasteEnd)
    646 	t.prepareXtermModifiers()
    647 	t.prepareBracketedPaste()
    648 	t.prepareCursorStyles()
    649 	t.prepareUnderlines()
    650 	t.prepareExtendedOSC()
    651 
    652 outer:
    653 	// Add key mappings for control keys.
    654 	for i := 0; i < ' '; i++ {
    655 		// Do not insert direct key codes for ambiguous keys.
    656 		// For example, ESC is used for lots of other keys, so
    657 		// when parsing this we don't want to fast path handling
    658 		// of it, but instead wait a bit before parsing it as in
    659 		// isolation.
    660 		for esc := range t.keycodes {
    661 			if []byte(esc)[0] == byte(i) {
    662 				continue outer
    663 			}
    664 		}
    665 
    666 		t.keyexist[Key(i)] = true
    667 
    668 		mod := ModCtrl
    669 		switch Key(i) {
    670 		case KeyBS, KeyTAB, KeyESC, KeyCR:
    671 			// directly type-able- no control sequence
    672 			mod = ModNone
    673 		}
    674 		t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod}
    675 	}
    676 }
    677 
    678 func (t *tScreen) Fini() {
    679 	t.finiOnce.Do(t.finish)
    680 }
    681 
    682 func (t *tScreen) finish() {
    683 	close(t.quit)
    684 	t.finalize()
    685 }
    686 
    687 func (t *tScreen) SetStyle(style Style) {
    688 	t.Lock()
    689 	if !t.fini {
    690 		t.style = style
    691 	}
    692 	t.Unlock()
    693 }
    694 
    695 func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
    696 
    697 	nb := make([]byte, 6)
    698 	ob := make([]byte, 6)
    699 	num := utf8.EncodeRune(ob, r)
    700 	ob = ob[:num]
    701 	dst := 0
    702 	var err error
    703 	if enc := t.encoder; enc != nil {
    704 		enc.Reset()
    705 		dst, _, err = enc.Transform(nb, ob, true)
    706 	}
    707 	if err != nil || dst == 0 || nb[0] == '\x1a' {
    708 		// Combining characters are elided
    709 		if len(buf) == 0 {
    710 			if acs, ok := t.acs[r]; ok {
    711 				buf = append(buf, []byte(acs)...)
    712 			} else if fb, ok := t.fallback[r]; ok {
    713 				buf = append(buf, []byte(fb)...)
    714 			} else {
    715 				buf = append(buf, '?')
    716 			}
    717 		}
    718 	} else {
    719 		buf = append(buf, nb[:dst]...)
    720 	}
    721 
    722 	return buf
    723 }
    724 
    725 func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
    726 	ti := t.ti
    727 	if ti.Colors == 0 {
    728 		// foreground vs background, we calculate luminance
    729 		// and possibly do a reverse video
    730 		if !fg.Valid() {
    731 			return attr
    732 		}
    733 		v, ok := t.colors[fg]
    734 		if !ok {
    735 			v = FindColor(fg, []Color{ColorBlack, ColorWhite})
    736 			t.colors[fg] = v
    737 		}
    738 		switch v {
    739 		case ColorWhite:
    740 			return attr
    741 		case ColorBlack:
    742 			return attr ^ AttrReverse
    743 		}
    744 	}
    745 
    746 	if fg == ColorReset || bg == ColorReset {
    747 		t.TPuts(ti.ResetFgBg)
    748 	}
    749 	if t.truecolor {
    750 		if ti.SetFgBgRGB != "" && fg.IsRGB() && bg.IsRGB() {
    751 			r1, g1, b1 := fg.RGB()
    752 			r2, g2, b2 := bg.RGB()
    753 			t.TPuts(ti.TParm(ti.SetFgBgRGB,
    754 				int(r1), int(g1), int(b1),
    755 				int(r2), int(g2), int(b2)))
    756 			return attr
    757 		}
    758 
    759 		if fg.IsRGB() && ti.SetFgRGB != "" {
    760 			r, g, b := fg.RGB()
    761 			t.TPuts(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b)))
    762 			fg = ColorDefault
    763 		}
    764 
    765 		if bg.IsRGB() && ti.SetBgRGB != "" {
    766 			r, g, b := bg.RGB()
    767 			t.TPuts(ti.TParm(ti.SetBgRGB,
    768 				int(r), int(g), int(b)))
    769 			bg = ColorDefault
    770 		}
    771 	}
    772 
    773 	if fg.Valid() {
    774 		if v, ok := t.colors[fg]; ok {
    775 			fg = v
    776 		} else {
    777 			v = FindColor(fg, t.palette)
    778 			t.colors[fg] = v
    779 			fg = v
    780 		}
    781 	}
    782 
    783 	if bg.Valid() {
    784 		if v, ok := t.colors[bg]; ok {
    785 			bg = v
    786 		} else {
    787 			v = FindColor(bg, t.palette)
    788 			t.colors[bg] = v
    789 			bg = v
    790 		}
    791 	}
    792 
    793 	if fg.Valid() && bg.Valid() && ti.SetFgBg != "" {
    794 		t.TPuts(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff)))
    795 	} else {
    796 		if fg.Valid() && ti.SetFg != "" {
    797 			t.TPuts(ti.TParm(ti.SetFg, int(fg&0xff)))
    798 		}
    799 		if bg.Valid() && ti.SetBg != "" {
    800 			t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff)))
    801 		}
    802 	}
    803 	return attr
    804 }
    805 
    806 func (t *tScreen) drawCell(x, y int) int {
    807 
    808 	ti := t.ti
    809 
    810 	mainc, combc, style, width := t.cells.GetContent(x, y)
    811 	if !t.cells.Dirty(x, y) {
    812 		return width
    813 	}
    814 
    815 	if y == t.h-1 && x == t.w-1 && t.ti.AutoMargin && ti.DisableAutoMargin == "" && ti.InsertChar != "" {
    816 		// our solution is somewhat goofy.
    817 		// we write to the second to the last cell what we want in the last cell, then we
    818 		// insert a character at that 2nd to last position to shift the last column into
    819 		// place, then we rewrite that 2nd to last cell.  Old terminals suck.
    820 		t.TPuts(ti.TGoto(x-1, y))
    821 		defer func() {
    822 			t.TPuts(ti.TGoto(x-1, y))
    823 			t.TPuts(ti.InsertChar)
    824 			t.cy = y
    825 			t.cx = x - 1
    826 			t.cells.SetDirty(x-1, y, true)
    827 			_ = t.drawCell(x-1, y)
    828 			t.TPuts(t.ti.TGoto(0, 0))
    829 			t.cy = 0
    830 			t.cx = 0
    831 		}()
    832 	} else if t.cy != y || t.cx != x {
    833 		t.TPuts(ti.TGoto(x, y))
    834 		t.cx = x
    835 		t.cy = y
    836 	}
    837 
    838 	if style == StyleDefault {
    839 		style = t.style
    840 	}
    841 	if style != t.curstyle {
    842 		fg, bg, attrs := style.fg, style.bg, style.attrs
    843 
    844 		t.TPuts(ti.AttrOff)
    845 
    846 		attrs = t.sendFgBg(fg, bg, attrs)
    847 		if attrs&AttrBold != 0 {
    848 			t.TPuts(ti.Bold)
    849 		}
    850 		if us, uc := style.ulStyle, style.ulColor; us != UnderlineStyleNone {
    851 			if t.underColor != "" || t.underRGB != "" {
    852 				if uc == ColorReset {
    853 					t.TPuts(t.underFg)
    854 				} else if uc.IsRGB() {
    855 					if t.underRGB != "" {
    856 						r, g, b := uc.RGB()
    857 						t.TPuts(ti.TParm(t.underRGB, int(r), int(g), int(b)))
    858 					} else {
    859 						if v, ok := t.colors[uc]; ok {
    860 							uc = v
    861 						} else {
    862 							v = FindColor(uc, t.palette)
    863 							t.colors[uc] = v
    864 							uc = v
    865 						}
    866 						t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
    867 					}
    868 				} else if uc.Valid() {
    869 					t.TPuts(ti.TParm(t.underColor, int(uc&0xff)))
    870 				}
    871 			}
    872 			t.TPuts(ti.Underline) // to ensure everyone gets at least a basic underline
    873 			switch us {
    874 			case UnderlineStyleDouble:
    875 				t.TPuts(t.doubleUnder)
    876 			case UnderlineStyleCurly:
    877 				t.TPuts(t.curlyUnder)
    878 			case UnderlineStyleDotted:
    879 				t.TPuts(t.dottedUnder)
    880 			case UnderlineStyleDashed:
    881 				t.TPuts(t.dashedUnder)
    882 			}
    883 		}
    884 		if attrs&AttrReverse != 0 {
    885 			t.TPuts(ti.Reverse)
    886 		}
    887 		if attrs&AttrBlink != 0 {
    888 			t.TPuts(ti.Blink)
    889 		}
    890 		if attrs&AttrDim != 0 {
    891 			t.TPuts(ti.Dim)
    892 		}
    893 		if attrs&AttrItalic != 0 {
    894 			t.TPuts(ti.Italic)
    895 		}
    896 		if attrs&AttrStrikeThrough != 0 {
    897 			t.TPuts(ti.StrikeThrough)
    898 		}
    899 
    900 		// URL string can be long, so don't send it unless we really need to
    901 		if t.enterUrl != "" && t.curstyle != style {
    902 			if style.url != "" {
    903 				t.TPuts(ti.TParm(t.enterUrl, style.url, style.urlId))
    904 			} else {
    905 				t.TPuts(t.exitUrl)
    906 			}
    907 		}
    908 
    909 		t.curstyle = style
    910 	}
    911 
    912 	// now emit runes - taking care to not overrun width with a
    913 	// wide character, and to ensure that we emit exactly one regular
    914 	// character followed up by any residual combing characters
    915 
    916 	if width < 1 {
    917 		width = 1
    918 	}
    919 
    920 	var str string
    921 
    922 	buf := make([]byte, 0, 6)
    923 
    924 	buf = t.encodeRune(mainc, buf)
    925 	for _, r := range combc {
    926 		buf = t.encodeRune(r, buf)
    927 	}
    928 
    929 	str = string(buf)
    930 	if width > 1 && str == "?" {
    931 		// No FullWidth character support
    932 		str = "? "
    933 		t.cx = -1
    934 	}
    935 
    936 	if x > t.w-width {
    937 		// too wide to fit; emit a single space instead
    938 		width = 1
    939 		str = " "
    940 	}
    941 	t.writeString(str)
    942 	t.cx += width
    943 	t.cells.SetDirty(x, y, false)
    944 	if width > 1 {
    945 		t.cx = -1
    946 	}
    947 
    948 	return width
    949 }
    950 
    951 func (t *tScreen) ShowCursor(x, y int) {
    952 	t.Lock()
    953 	t.cursorx = x
    954 	t.cursory = y
    955 	t.Unlock()
    956 }
    957 
    958 func (t *tScreen) SetCursor(cs CursorStyle, cc Color) {
    959 	t.Lock()
    960 	t.cursorStyle = cs
    961 	t.cursorColor = cc
    962 	t.Unlock()
    963 }
    964 
    965 func (t *tScreen) HideCursor() {
    966 	t.ShowCursor(-1, -1)
    967 }
    968 
    969 func (t *tScreen) showCursor() {
    970 
    971 	x, y := t.cursorx, t.cursory
    972 	w, h := t.cells.Size()
    973 	if x < 0 || y < 0 || x >= w || y >= h {
    974 		t.hideCursor()
    975 		return
    976 	}
    977 	t.TPuts(t.ti.TGoto(x, y))
    978 	t.TPuts(t.ti.ShowCursor)
    979 	if t.cursorStyles != nil {
    980 		if esc, ok := t.cursorStyles[t.cursorStyle]; ok {
    981 			t.TPuts(esc)
    982 		}
    983 	}
    984 	if t.cursorRGB != "" {
    985 		if t.cursorColor == ColorReset {
    986 			t.TPuts(t.cursorFg)
    987 		} else if t.cursorColor.Valid() {
    988 			r, g, b := t.cursorColor.RGB()
    989 			t.TPuts(t.ti.TParm(t.cursorRGB, int(r), int(g), int(b)))
    990 		}
    991 	}
    992 	t.cx = x
    993 	t.cy = y
    994 }
    995 
    996 // writeString sends a string to the terminal. The string is sent as-is and
    997 // this function does not expand inline padding indications (of the form
    998 // $<[delay]> where [delay] is msec). In order to have these expanded, use
    999 // TPuts. If the screen is "buffering", the string is collected in a buffer,
   1000 // with the intention that the entire buffer be sent to the terminal in one
   1001 // write operation at some point later.
   1002 func (t *tScreen) writeString(s string) {
   1003 	if t.buffering {
   1004 		_, _ = io.WriteString(&t.buf, s)
   1005 	} else {
   1006 		_, _ = io.WriteString(t.tty, s)
   1007 	}
   1008 }
   1009 
   1010 func (t *tScreen) TPuts(s string) {
   1011 	if t.buffering {
   1012 		t.ti.TPuts(&t.buf, s)
   1013 	} else {
   1014 		t.ti.TPuts(t.tty, s)
   1015 	}
   1016 }
   1017 
   1018 func (t *tScreen) Show() {
   1019 	t.Lock()
   1020 	if !t.fini {
   1021 		t.resize()
   1022 		t.draw()
   1023 	}
   1024 	t.Unlock()
   1025 }
   1026 
   1027 func (t *tScreen) clearScreen() {
   1028 	t.TPuts(t.ti.AttrOff)
   1029 	t.TPuts(t.exitUrl)
   1030 	_ = t.sendFgBg(t.style.fg, t.style.bg, AttrNone)
   1031 	t.TPuts(t.ti.Clear)
   1032 	t.clear = false
   1033 }
   1034 
   1035 func (t *tScreen) hideCursor() {
   1036 	// does not update cursor position
   1037 	if t.ti.HideCursor != "" {
   1038 		t.TPuts(t.ti.HideCursor)
   1039 	} else {
   1040 		// No way to hide cursor, stick it
   1041 		// at bottom right of screen
   1042 		t.cx, t.cy = t.cells.Size()
   1043 		t.TPuts(t.ti.TGoto(t.cx, t.cy))
   1044 	}
   1045 }
   1046 
   1047 func (t *tScreen) draw() {
   1048 	// clobber cursor position, because we're going to change it all
   1049 	t.cx = -1
   1050 	t.cy = -1
   1051 	// make no style assumptions
   1052 	t.curstyle = styleInvalid
   1053 
   1054 	t.buf.Reset()
   1055 	t.buffering = true
   1056 	defer func() {
   1057 		t.buffering = false
   1058 	}()
   1059 
   1060 	// hide the cursor while we move stuff around
   1061 	t.hideCursor()
   1062 
   1063 	if t.clear {
   1064 		t.clearScreen()
   1065 	}
   1066 
   1067 	for y := 0; y < t.h; y++ {
   1068 		for x := 0; x < t.w; x++ {
   1069 			width := t.drawCell(x, y)
   1070 			if width > 1 {
   1071 				if x+1 < t.w {
   1072 					// this is necessary so that if we ever
   1073 					// go back to drawing that cell, we
   1074 					// actually will *draw* it.
   1075 					t.cells.SetDirty(x+1, y, true)
   1076 				}
   1077 			}
   1078 			x += width - 1
   1079 		}
   1080 	}
   1081 
   1082 	// restore the cursor
   1083 	t.showCursor()
   1084 
   1085 	_, _ = t.buf.WriteTo(t.tty)
   1086 }
   1087 
   1088 func (t *tScreen) EnableMouse(flags ...MouseFlags) {
   1089 	var f MouseFlags
   1090 	flagsPresent := false
   1091 	for _, flag := range flags {
   1092 		f |= flag
   1093 		flagsPresent = true
   1094 	}
   1095 	if !flagsPresent {
   1096 		f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
   1097 	}
   1098 
   1099 	t.Lock()
   1100 	t.mouseFlags = f
   1101 	t.enableMouse(f)
   1102 	t.Unlock()
   1103 }
   1104 
   1105 func (t *tScreen) enableMouse(f MouseFlags) {
   1106 	// Rather than using terminfo to find mouse escape sequences, we rely on the fact that
   1107 	// pretty much *every* terminal that supports mouse tracking follows the
   1108 	// XTerm standards (the modern ones).
   1109 	if len(t.mouse) != 0 {
   1110 		// start by disabling all tracking.
   1111 		t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l")
   1112 		if f&MouseButtonEvents != 0 {
   1113 			t.TPuts("\x1b[?1000h")
   1114 		}
   1115 		if f&MouseDragEvents != 0 {
   1116 			t.TPuts("\x1b[?1002h")
   1117 		}
   1118 		if f&MouseMotionEvents != 0 {
   1119 			t.TPuts("\x1b[?1003h")
   1120 		}
   1121 		if f&(MouseButtonEvents|MouseDragEvents|MouseMotionEvents) != 0 {
   1122 			t.TPuts("\x1b[?1006h")
   1123 		}
   1124 	}
   1125 
   1126 }
   1127 
   1128 func (t *tScreen) DisableMouse() {
   1129 	t.Lock()
   1130 	t.mouseFlags = 0
   1131 	t.enableMouse(0)
   1132 	t.Unlock()
   1133 }
   1134 
   1135 func (t *tScreen) EnablePaste() {
   1136 	t.Lock()
   1137 	t.pasteEnabled = true
   1138 	t.enablePasting(true)
   1139 	t.Unlock()
   1140 }
   1141 
   1142 func (t *tScreen) DisablePaste() {
   1143 	t.Lock()
   1144 	t.pasteEnabled = false
   1145 	t.enablePasting(false)
   1146 	t.Unlock()
   1147 }
   1148 
   1149 func (t *tScreen) enablePasting(on bool) {
   1150 	var s string
   1151 	if on {
   1152 		s = t.enablePaste
   1153 	} else {
   1154 		s = t.disablePaste
   1155 	}
   1156 	if s != "" {
   1157 		t.TPuts(s)
   1158 	}
   1159 }
   1160 
   1161 func (t *tScreen) EnableFocus() {
   1162 	t.Lock()
   1163 	t.focusEnabled = true
   1164 	t.enableFocusReporting()
   1165 	t.Unlock()
   1166 }
   1167 
   1168 func (t *tScreen) DisableFocus() {
   1169 	t.Lock()
   1170 	t.focusEnabled = false
   1171 	t.disableFocusReporting()
   1172 	t.Unlock()
   1173 }
   1174 
   1175 func (t *tScreen) enableFocusReporting() {
   1176 	if t.enableFocus != "" {
   1177 		t.TPuts(t.enableFocus)
   1178 	}
   1179 }
   1180 
   1181 func (t *tScreen) disableFocusReporting() {
   1182 	if t.disableFocus != "" {
   1183 		t.TPuts(t.disableFocus)
   1184 	}
   1185 }
   1186 
   1187 func (t *tScreen) Size() (int, int) {
   1188 	t.Lock()
   1189 	w, h := t.w, t.h
   1190 	t.Unlock()
   1191 	return w, h
   1192 }
   1193 
   1194 func (t *tScreen) resize() {
   1195 	ws, err := t.tty.WindowSize()
   1196 	if err != nil {
   1197 		return
   1198 	}
   1199 	if ws.Width == t.w && ws.Height == t.h {
   1200 		return
   1201 	}
   1202 	t.cx = -1
   1203 	t.cy = -1
   1204 
   1205 	t.cells.Resize(ws.Width, ws.Height)
   1206 	t.cells.Invalidate()
   1207 	t.h = ws.Height
   1208 	t.w = ws.Width
   1209 	ev := &EventResize{t: time.Now(), ws: ws}
   1210 	select {
   1211 	case t.eventQ <- ev:
   1212 	default:
   1213 	}
   1214 }
   1215 
   1216 func (t *tScreen) Colors() int {
   1217 	// this doesn't change, no need for lock
   1218 	if t.truecolor {
   1219 		return 1 << 24
   1220 	}
   1221 	return t.ti.Colors
   1222 }
   1223 
   1224 // nColors returns the size of the built-in palette.
   1225 // This is distinct from Colors(), as it will generally
   1226 // always be a small number. (<= 256)
   1227 func (t *tScreen) nColors() int {
   1228 	return t.ti.Colors
   1229 }
   1230 
   1231 // vtACSNames is a map of bytes defined by terminfo that are used in
   1232 // the terminals Alternate Character Set to represent other glyphs.
   1233 // For example, the upper left corner of the box drawing set can be
   1234 // displayed by printing "l" while in the alternate character set.
   1235 // It's not quite that simple, since the "l" is the terminfo name,
   1236 // and it may be necessary to use a different character based on
   1237 // the terminal implementation (or the terminal may lack support for
   1238 // this altogether).  See buildAcsMap below for detail.
   1239 var vtACSNames = map[byte]rune{
   1240 	'+': RuneRArrow,
   1241 	',': RuneLArrow,
   1242 	'-': RuneUArrow,
   1243 	'.': RuneDArrow,
   1244 	'0': RuneBlock,
   1245 	'`': RuneDiamond,
   1246 	'a': RuneCkBoard,
   1247 	'b': '␉', // VT100, Not defined by terminfo
   1248 	'c': '␌', // VT100, Not defined by terminfo
   1249 	'd': '␋', // VT100, Not defined by terminfo
   1250 	'e': '␊', // VT100, Not defined by terminfo
   1251 	'f': RuneDegree,
   1252 	'g': RunePlMinus,
   1253 	'h': RuneBoard,
   1254 	'i': RuneLantern,
   1255 	'j': RuneLRCorner,
   1256 	'k': RuneURCorner,
   1257 	'l': RuneULCorner,
   1258 	'm': RuneLLCorner,
   1259 	'n': RunePlus,
   1260 	'o': RuneS1,
   1261 	'p': RuneS3,
   1262 	'q': RuneHLine,
   1263 	'r': RuneS7,
   1264 	's': RuneS9,
   1265 	't': RuneLTee,
   1266 	'u': RuneRTee,
   1267 	'v': RuneBTee,
   1268 	'w': RuneTTee,
   1269 	'x': RuneVLine,
   1270 	'y': RuneLEqual,
   1271 	'z': RuneGEqual,
   1272 	'{': RunePi,
   1273 	'|': RuneNEqual,
   1274 	'}': RuneSterling,
   1275 	'~': RuneBullet,
   1276 }
   1277 
   1278 // buildAcsMap builds a map of characters that we translate from Unicode to
   1279 // alternate character encodings.  To do this, we use the standard VT100 ACS
   1280 // maps.  This is only done if the terminal lacks support for Unicode; we
   1281 // always prefer to emit Unicode glyphs when we are able.
   1282 func (t *tScreen) buildAcsMap() {
   1283 	acsstr := t.ti.AltChars
   1284 	t.acs = make(map[rune]string)
   1285 	for len(acsstr) > 2 {
   1286 		srcv := acsstr[0]
   1287 		dstv := string(acsstr[1])
   1288 		if r, ok := vtACSNames[srcv]; ok {
   1289 			t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs
   1290 		}
   1291 		acsstr = acsstr[2:]
   1292 	}
   1293 }
   1294 
   1295 func (t *tScreen) clip(x, y int) (int, int) {
   1296 	w, h := t.cells.Size()
   1297 	if x < 0 {
   1298 		x = 0
   1299 	}
   1300 	if y < 0 {
   1301 		y = 0
   1302 	}
   1303 	if x > w-1 {
   1304 		x = w - 1
   1305 	}
   1306 	if y > h-1 {
   1307 		y = h - 1
   1308 	}
   1309 	return x, y
   1310 }
   1311 
   1312 // buildMouseEvent returns an event based on the supplied coordinates and button
   1313 // state. Note that the screen's mouse button state is updated based on the
   1314 // input to this function (i.e. it mutates the receiver).
   1315 func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse {
   1316 
   1317 	// XTerm mouse events only report at most one button at a time,
   1318 	// which may include a wheel button.  Wheel motion events are
   1319 	// reported as single impulses, while other button events are reported
   1320 	// as separate press & release events.
   1321 
   1322 	button := ButtonNone
   1323 	mod := ModNone
   1324 
   1325 	// Mouse wheel has bit 6 set, no release events.  It should be noted
   1326 	// that wheel events are sometimes misdelivered as mouse button events
   1327 	// during a click-drag, so we debounce these, considering them to be
   1328 	// button press events unless we see an intervening release event.
   1329 	switch btn & 0x43 {
   1330 	case 0:
   1331 		button = Button1
   1332 	case 1:
   1333 		button = Button3 // Note we prefer to treat right as button 2
   1334 	case 2:
   1335 		button = Button2 // And the middle button as button 3
   1336 	case 3:
   1337 		button = ButtonNone
   1338 	case 0x40:
   1339 		button = WheelUp
   1340 	case 0x41:
   1341 		button = WheelDown
   1342 	}
   1343 
   1344 	if btn&0x4 != 0 {
   1345 		mod |= ModShift
   1346 	}
   1347 	if btn&0x8 != 0 {
   1348 		mod |= ModAlt
   1349 	}
   1350 	if btn&0x10 != 0 {
   1351 		mod |= ModCtrl
   1352 	}
   1353 
   1354 	// Some terminals will report mouse coordinates outside the
   1355 	// screen, especially with click-drag events.  Clip the coordinates
   1356 	// to the screen in that case.
   1357 	x, y = t.clip(x, y)
   1358 
   1359 	return NewEventMouse(x, y, button, mod)
   1360 }
   1361 
   1362 // parseSgrMouse attempts to locate an SGR mouse record at the start of the
   1363 // buffer.  It returns true, true if it found one, and the associated bytes
   1364 // be removed from the buffer.  It returns true, false if the buffer might
   1365 // contain such an event, but more bytes are necessary (partial match), and
   1366 // false, false if the content is definitely *not* an SGR mouse record.
   1367 func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1368 
   1369 	b := buf.Bytes()
   1370 
   1371 	var x, y, btn, state int
   1372 	dig := false
   1373 	neg := false
   1374 	motion := false
   1375 	scroll := false
   1376 	i := 0
   1377 	val := 0
   1378 
   1379 	for i = range b {
   1380 		switch b[i] {
   1381 		case '\x1b':
   1382 			if state != 0 {
   1383 				return false, false
   1384 			}
   1385 			state = 1
   1386 
   1387 		case '\x9b':
   1388 			if state != 0 {
   1389 				return false, false
   1390 			}
   1391 			state = 2
   1392 
   1393 		case '[':
   1394 			if state != 1 {
   1395 				return false, false
   1396 			}
   1397 			state = 2
   1398 
   1399 		case '<':
   1400 			if state != 2 {
   1401 				return false, false
   1402 			}
   1403 			val = 0
   1404 			dig = false
   1405 			neg = false
   1406 			state = 3
   1407 
   1408 		case '-':
   1409 			if state != 3 && state != 4 && state != 5 {
   1410 				return false, false
   1411 			}
   1412 			if dig || neg {
   1413 				return false, false
   1414 			}
   1415 			neg = true // stay in state
   1416 
   1417 		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   1418 			if state != 3 && state != 4 && state != 5 {
   1419 				return false, false
   1420 			}
   1421 			val *= 10
   1422 			val += int(b[i] - '0')
   1423 			dig = true // stay in state
   1424 
   1425 		case ';':
   1426 			if neg {
   1427 				val = -val
   1428 			}
   1429 			switch state {
   1430 			case 3:
   1431 				btn, val = val, 0
   1432 				neg, dig, state = false, false, 4
   1433 			case 4:
   1434 				x, val = val-1, 0
   1435 				neg, dig, state = false, false, 5
   1436 			default:
   1437 				return false, false
   1438 			}
   1439 
   1440 		case 'm', 'M':
   1441 			if state != 5 {
   1442 				return false, false
   1443 			}
   1444 			if neg {
   1445 				val = -val
   1446 			}
   1447 			y = val - 1
   1448 
   1449 			motion = (btn & 32) != 0
   1450 			scroll = (btn & 0x42) == 0x40
   1451 			btn &^= 32
   1452 			if b[i] == 'm' {
   1453 				// mouse release, clear all buttons
   1454 				btn |= 3
   1455 				btn &^= 0x40
   1456 				t.buttondn = false
   1457 			} else if motion {
   1458 				/*
   1459 				 * Some broken terminals appear to send
   1460 				 * mouse button one motion events, instead of
   1461 				 * encoding 35 (no buttons) into these events.
   1462 				 * We resolve these by looking for a non-motion
   1463 				 * event first.
   1464 				 */
   1465 				if !t.buttondn {
   1466 					btn |= 3
   1467 					btn &^= 0x40
   1468 				}
   1469 			} else if !scroll {
   1470 				t.buttondn = true
   1471 			}
   1472 			// consume the event bytes
   1473 			for i >= 0 {
   1474 				_, _ = buf.ReadByte()
   1475 				i--
   1476 			}
   1477 			*evs = append(*evs, t.buildMouseEvent(x, y, btn))
   1478 			return true, true
   1479 		}
   1480 	}
   1481 
   1482 	// incomplete & inconclusive at this point
   1483 	return true, false
   1484 }
   1485 
   1486 func (t *tScreen) parseFocus(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1487 	state := 0
   1488 	b := buf.Bytes()
   1489 	for i := range b {
   1490 		switch state {
   1491 		case 0:
   1492 			if b[i] != '\x1b' {
   1493 				return false, false
   1494 			}
   1495 			state = 1
   1496 		case 1:
   1497 			if b[i] != '[' {
   1498 				return false, false
   1499 			}
   1500 			state = 2
   1501 		case 2:
   1502 			if b[i] != 'I' && b[i] != 'O' {
   1503 				return false, false
   1504 			}
   1505 			*evs = append(*evs, NewEventFocus(b[i] == 'I'))
   1506 			_, _ = buf.ReadByte()
   1507 			_, _ = buf.ReadByte()
   1508 			_, _ = buf.ReadByte()
   1509 			return true, true
   1510 		}
   1511 	}
   1512 	return true, false
   1513 }
   1514 
   1515 func (t *tScreen) parseClipboard(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1516 	b := buf.Bytes()
   1517 	state := 0
   1518 	prefix := []byte("\x1b]52;c;")
   1519 
   1520 	if len(prefix) >= len(b) {
   1521 		if bytes.HasPrefix(prefix, b) {
   1522 			// inconclusive so far
   1523 			return true, false
   1524 		}
   1525 		// definitely not a match
   1526 		return false, false
   1527 	}
   1528 	b = b[len(prefix):]
   1529 
   1530 	for _, c := range b {
   1531 		// valid base64 digits
   1532 		if state == 0 {
   1533 			if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=') {
   1534 				continue
   1535 			}
   1536 			if c == '\x1b' {
   1537 				state = 1
   1538 				continue
   1539 			}
   1540 			if c == '\a' {
   1541 				// matched with BEL instead of ST
   1542 				b = b[:len(b)-1] // drop the trailing BEL
   1543 				decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b)))
   1544 				if num, err := base64.StdEncoding.Decode(decoded, b); err == nil {
   1545 					*evs = append(*evs, NewEventClipboard(decoded[:num]))
   1546 				}
   1547 				_, _ = buf.ReadBytes('\a')
   1548 				return true, true
   1549 			}
   1550 			return false, false
   1551 		}
   1552 		if state == 1 {
   1553 			if c == '\\' {
   1554 				b = b[:len(b)-2] // drop the trailing ST (\x1b\\)
   1555 				// now decode the data
   1556 				decoded := make([]byte, base64.StdEncoding.DecodedLen(len(b)))
   1557 				if num, err := base64.StdEncoding.Decode(decoded, b); err == nil {
   1558 					*evs = append(*evs, NewEventClipboard(decoded[:num]))
   1559 				}
   1560 				_, _ = buf.ReadBytes('\\')
   1561 				return true, true
   1562 			}
   1563 			return false, false
   1564 		}
   1565 	}
   1566 	// not enough data yet (not terminated)
   1567 	return true, false
   1568 }
   1569 
   1570 // parseXtermMouse is like parseSgrMouse, but it parses a legacy
   1571 // X11 mouse record.
   1572 func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1573 
   1574 	b := buf.Bytes()
   1575 
   1576 	state := 0
   1577 	btn := 0
   1578 	x := 0
   1579 	y := 0
   1580 
   1581 	for i := range b {
   1582 		switch state {
   1583 		case 0:
   1584 			switch b[i] {
   1585 			case '\x1b':
   1586 				state = 1
   1587 			case '\x9b':
   1588 				state = 2
   1589 			default:
   1590 				return false, false
   1591 			}
   1592 		case 1:
   1593 			if b[i] != '[' {
   1594 				return false, false
   1595 			}
   1596 			state = 2
   1597 		case 2:
   1598 			if b[i] != 'M' {
   1599 				return false, false
   1600 			}
   1601 			state++
   1602 		case 3:
   1603 			btn = int(b[i])
   1604 			state++
   1605 		case 4:
   1606 			x = int(b[i]) - 32 - 1
   1607 			state++
   1608 		case 5:
   1609 			y = int(b[i]) - 32 - 1
   1610 			for i >= 0 {
   1611 				_, _ = buf.ReadByte()
   1612 				i--
   1613 			}
   1614 			*evs = append(*evs, t.buildMouseEvent(x, y, btn))
   1615 			return true, true
   1616 		}
   1617 	}
   1618 	return true, false
   1619 }
   1620 
   1621 func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1622 	b := buf.Bytes()
   1623 	partial := false
   1624 	for e, k := range t.keycodes {
   1625 		esc := []byte(e)
   1626 		if (len(esc) == 1) && (esc[0] == '\x1b') {
   1627 			continue
   1628 		}
   1629 		if bytes.HasPrefix(b, esc) {
   1630 			// matched
   1631 			var r rune
   1632 			if len(esc) == 1 {
   1633 				r = rune(b[0])
   1634 			}
   1635 			mod := k.mod
   1636 			if t.escaped {
   1637 				mod |= ModAlt
   1638 				t.escaped = false
   1639 			}
   1640 			switch k.key {
   1641 			case keyPasteStart:
   1642 				*evs = append(*evs, NewEventPaste(true))
   1643 			case keyPasteEnd:
   1644 				*evs = append(*evs, NewEventPaste(false))
   1645 			default:
   1646 				*evs = append(*evs, NewEventKey(k.key, r, mod))
   1647 			}
   1648 			for i := 0; i < len(esc); i++ {
   1649 				_, _ = buf.ReadByte()
   1650 			}
   1651 			return true, true
   1652 		}
   1653 		if bytes.HasPrefix(esc, b) {
   1654 			partial = true
   1655 		}
   1656 	}
   1657 	return partial, false
   1658 }
   1659 
   1660 func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) {
   1661 	b := buf.Bytes()
   1662 	if b[0] >= ' ' && b[0] <= 0x7F {
   1663 		// printable ASCII easy to deal with -- no encodings
   1664 		mod := ModNone
   1665 		if t.escaped {
   1666 			mod = ModAlt
   1667 			t.escaped = false
   1668 		}
   1669 		*evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod))
   1670 		_, _ = buf.ReadByte()
   1671 		return true, true
   1672 	}
   1673 
   1674 	if b[0] < 0x80 {
   1675 		// Low numbered values are control keys, not runes.
   1676 		return false, false
   1677 	}
   1678 
   1679 	utf := make([]byte, 12)
   1680 	for l := 1; l <= len(b); l++ {
   1681 		t.decoder.Reset()
   1682 		nOut, nIn, e := t.decoder.Transform(utf, b[:l], true)
   1683 		if e == transform.ErrShortSrc {
   1684 			continue
   1685 		}
   1686 		if nOut != 0 {
   1687 			r, _ := utf8.DecodeRune(utf[:nOut])
   1688 			if r != utf8.RuneError {
   1689 				mod := ModNone
   1690 				if t.escaped {
   1691 					mod = ModAlt
   1692 					t.escaped = false
   1693 				}
   1694 				*evs = append(*evs, NewEventKey(KeyRune, r, mod))
   1695 			}
   1696 			for nIn > 0 {
   1697 				_, _ = buf.ReadByte()
   1698 				nIn--
   1699 			}
   1700 			return true, true
   1701 		}
   1702 	}
   1703 	// Looks like potential escape
   1704 	return true, false
   1705 }
   1706 
   1707 func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) {
   1708 	evs := t.collectEventsFromInput(buf, expire)
   1709 
   1710 	for _, ev := range evs {
   1711 		select {
   1712 		case t.eventQ <- ev:
   1713 		case <-t.quit:
   1714 			return
   1715 		}
   1716 	}
   1717 }
   1718 
   1719 // Return an array of Events extracted from the supplied buffer. This is done
   1720 // while holding the screen's lock - the events can then be queued for
   1721 // application processing with the lock released.
   1722 func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event {
   1723 
   1724 	res := make([]Event, 0, 20)
   1725 
   1726 	t.Lock()
   1727 	defer t.Unlock()
   1728 
   1729 	for {
   1730 		b := buf.Bytes()
   1731 		if len(b) == 0 {
   1732 			buf.Reset()
   1733 			return res
   1734 		}
   1735 
   1736 		partials := 0
   1737 
   1738 		if part, comp := t.parseRune(buf, &res); comp {
   1739 			continue
   1740 		} else if part {
   1741 			partials++
   1742 		}
   1743 
   1744 		if part, comp := t.parseFunctionKey(buf, &res); comp {
   1745 			continue
   1746 		} else if part {
   1747 			partials++
   1748 		}
   1749 
   1750 		if part, comp := t.parseFocus(buf, &res); comp {
   1751 			continue
   1752 		} else if part {
   1753 			partials++
   1754 		}
   1755 
   1756 		// Only parse mouse records if this term claims to have
   1757 		// mouse support
   1758 
   1759 		if t.ti.Mouse != "" {
   1760 			if part, comp := t.parseXtermMouse(buf, &res); comp {
   1761 				continue
   1762 			} else if part {
   1763 				partials++
   1764 			}
   1765 
   1766 			if part, comp := t.parseSgrMouse(buf, &res); comp {
   1767 				continue
   1768 			} else if part {
   1769 				partials++
   1770 			}
   1771 		}
   1772 
   1773 		if t.setClipboard != "" {
   1774 			if part, comp := t.parseClipboard(buf, &res); comp {
   1775 				continue
   1776 			} else if part {
   1777 				partials++
   1778 			}
   1779 		}
   1780 
   1781 		if partials == 0 || expire {
   1782 			if b[0] == '\x1b' {
   1783 				if len(b) == 1 {
   1784 					res = append(res, NewEventKey(KeyEsc, 0, ModNone))
   1785 					t.escaped = false
   1786 				} else {
   1787 					t.escaped = true
   1788 				}
   1789 				_, _ = buf.ReadByte()
   1790 				continue
   1791 			}
   1792 			// Nothing was going to match, or we timed-out
   1793 			// waiting for more data -- just deliver the characters
   1794 			// to the app & let them sort it out.  Possibly we
   1795 			// should only do this for control characters like ESC.
   1796 			by, _ := buf.ReadByte()
   1797 			mod := ModNone
   1798 			if t.escaped {
   1799 				t.escaped = false
   1800 				mod = ModAlt
   1801 			}
   1802 			res = append(res, NewEventKey(KeyRune, rune(by), mod))
   1803 			continue
   1804 		}
   1805 
   1806 		// well we have some partial data, wait until we get
   1807 		// some more
   1808 		break
   1809 	}
   1810 
   1811 	return res
   1812 }
   1813 
   1814 func (t *tScreen) mainLoop(stopQ chan struct{}) {
   1815 	defer t.wg.Done()
   1816 	buf := &bytes.Buffer{}
   1817 	for {
   1818 		select {
   1819 		case <-stopQ:
   1820 			return
   1821 		case <-t.quit:
   1822 			return
   1823 		case <-t.resizeQ:
   1824 			t.Lock()
   1825 			t.cx = -1
   1826 			t.cy = -1
   1827 			t.resize()
   1828 			t.cells.Invalidate()
   1829 			t.draw()
   1830 			t.Unlock()
   1831 			continue
   1832 		case <-t.keytimer.C:
   1833 			// If the timer fired, and the current time
   1834 			// is after the expiration of the escape sequence,
   1835 			// then we assume the escape sequence reached its
   1836 			// conclusion, and process the chunk independently.
   1837 			// This lets us detect conflicts such as a lone ESC.
   1838 			if buf.Len() > 0 {
   1839 				if time.Now().After(t.keyexpire) {
   1840 					t.scanInput(buf, true)
   1841 				}
   1842 			}
   1843 			if buf.Len() > 0 {
   1844 				if !t.keytimer.Stop() {
   1845 					select {
   1846 					case <-t.keytimer.C:
   1847 					default:
   1848 					}
   1849 				}
   1850 				t.keytimer.Reset(time.Millisecond * 50)
   1851 			}
   1852 		case chunk := <-t.keychan:
   1853 			buf.Write(chunk)
   1854 			t.keyexpire = time.Now().Add(time.Millisecond * 50)
   1855 			t.scanInput(buf, false)
   1856 			if !t.keytimer.Stop() {
   1857 				select {
   1858 				case <-t.keytimer.C:
   1859 				default:
   1860 				}
   1861 			}
   1862 			if buf.Len() > 0 {
   1863 				t.keytimer.Reset(time.Millisecond * 50)
   1864 			}
   1865 		}
   1866 	}
   1867 }
   1868 
   1869 func (t *tScreen) inputLoop(stopQ chan struct{}) {
   1870 
   1871 	defer t.wg.Done()
   1872 	for {
   1873 		select {
   1874 		case <-stopQ:
   1875 			return
   1876 		default:
   1877 		}
   1878 		chunk := make([]byte, 128)
   1879 		n, e := t.tty.Read(chunk)
   1880 		switch e {
   1881 		case nil:
   1882 		default:
   1883 			t.Lock()
   1884 			running := t.running
   1885 			t.Unlock()
   1886 			if running {
   1887 				select {
   1888 				case t.eventQ <- NewEventError(e):
   1889 				case <-t.quit:
   1890 				}
   1891 			}
   1892 			return
   1893 		}
   1894 		if n > 0 {
   1895 			t.keychan <- chunk[:n]
   1896 		}
   1897 	}
   1898 }
   1899 
   1900 func (t *tScreen) Sync() {
   1901 	t.Lock()
   1902 	t.cx = -1
   1903 	t.cy = -1
   1904 	if !t.fini {
   1905 		t.resize()
   1906 		t.clear = true
   1907 		t.cells.Invalidate()
   1908 		t.draw()
   1909 	}
   1910 	t.Unlock()
   1911 }
   1912 
   1913 func (t *tScreen) CharacterSet() string {
   1914 	return t.charset
   1915 }
   1916 
   1917 func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) {
   1918 	t.Lock()
   1919 	t.fallback[orig] = fallback
   1920 	t.Unlock()
   1921 }
   1922 
   1923 func (t *tScreen) UnregisterRuneFallback(orig rune) {
   1924 	t.Lock()
   1925 	delete(t.fallback, orig)
   1926 	t.Unlock()
   1927 }
   1928 
   1929 func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
   1930 
   1931 	if enc := t.encoder; enc != nil {
   1932 		nb := make([]byte, 6)
   1933 		ob := make([]byte, 6)
   1934 		num := utf8.EncodeRune(ob, r)
   1935 
   1936 		enc.Reset()
   1937 		dst, _, err := enc.Transform(nb, ob[:num], true)
   1938 		if dst != 0 && err == nil && nb[0] != '\x1A' {
   1939 			return true
   1940 		}
   1941 	}
   1942 	// Terminal fallbacks always permitted, since we assume they are
   1943 	// basically nearly perfect renditions.
   1944 	if _, ok := t.acs[r]; ok {
   1945 		return true
   1946 	}
   1947 	if !checkFallbacks {
   1948 		return false
   1949 	}
   1950 	if _, ok := t.fallback[r]; ok {
   1951 		return true
   1952 	}
   1953 	return false
   1954 }
   1955 
   1956 func (t *tScreen) HasMouse() bool {
   1957 	return len(t.mouse) != 0
   1958 }
   1959 
   1960 func (t *tScreen) HasKey(k Key) bool {
   1961 	if k == KeyRune {
   1962 		return true
   1963 	}
   1964 	return t.keyexist[k]
   1965 }
   1966 
   1967 func (t *tScreen) SetSize(w, h int) {
   1968 	if t.setWinSize != "" {
   1969 		t.TPuts(t.ti.TParm(t.setWinSize, w, h))
   1970 	}
   1971 	t.cells.Invalidate()
   1972 	t.resize()
   1973 }
   1974 
   1975 func (t *tScreen) Resize(int, int, int, int) {}
   1976 
   1977 func (t *tScreen) Suspend() error {
   1978 	t.disengage()
   1979 	return nil
   1980 }
   1981 
   1982 func (t *tScreen) Resume() error {
   1983 	return t.engage()
   1984 }
   1985 
   1986 func (t *tScreen) Tty() (Tty, bool) {
   1987 	return t.tty, true
   1988 }
   1989 
   1990 // engage is used to place the terminal in raw mode and establish screen size, etc.
   1991 // Think of this is as tcell "engaging" the clutch, as it's going to be driving the
   1992 // terminal interface.
   1993 func (t *tScreen) engage() error {
   1994 	t.Lock()
   1995 	defer t.Unlock()
   1996 	if t.tty == nil {
   1997 		return ErrNoScreen
   1998 	}
   1999 	t.tty.NotifyResize(func() {
   2000 		select {
   2001 		case t.resizeQ <- true:
   2002 		default:
   2003 		}
   2004 	})
   2005 	if t.running {
   2006 		return errors.New("already engaged")
   2007 	}
   2008 	if err := t.tty.Start(); err != nil {
   2009 		return err
   2010 	}
   2011 	t.running = true
   2012 	if ws, err := t.tty.WindowSize(); err == nil && ws.Width != 0 && ws.Height != 0 {
   2013 		t.cells.Resize(ws.Width, ws.Height)
   2014 	}
   2015 	stopQ := make(chan struct{})
   2016 	t.stopQ = stopQ
   2017 	t.enableMouse(t.mouseFlags)
   2018 	t.enablePasting(t.pasteEnabled)
   2019 	if t.focusEnabled {
   2020 		t.enableFocusReporting()
   2021 	}
   2022 
   2023 	ti := t.ti
   2024 	if os.Getenv("TCELL_ALTSCREEN") != "disable" {
   2025 		// Technically this may not be right, but every terminal we know about
   2026 		// (even Wyse 60) uses this to enter the alternate screen buffer, and
   2027 		// possibly save and restore the window title and/or icon.
   2028 		// (In theory there could be terminals that don't support X,Y cursor
   2029 		// positions without a setup command, but we don't support them.)
   2030 		t.TPuts(ti.EnterCA)
   2031 		if t.saveTitle != "" {
   2032 			t.TPuts(t.saveTitle)
   2033 		}
   2034 	}
   2035 	t.TPuts(ti.EnterKeypad)
   2036 	t.TPuts(ti.HideCursor)
   2037 	t.TPuts(ti.EnableAcs)
   2038 	t.TPuts(ti.DisableAutoMargin)
   2039 	t.TPuts(ti.Clear)
   2040 	if t.title != "" && t.setTitle != "" {
   2041 		t.TPuts(t.ti.TParm(t.setTitle, t.title))
   2042 	}
   2043 
   2044 	t.wg.Add(2)
   2045 	go t.inputLoop(stopQ)
   2046 	go t.mainLoop(stopQ)
   2047 	return nil
   2048 }
   2049 
   2050 // disengage is used to release the terminal back to support from the caller.
   2051 // Think of this as tcell disengaging the clutch, so that another application
   2052 // can take over the terminal interface.  This restores the TTY mode that was
   2053 // present when the application was first started.
   2054 func (t *tScreen) disengage() {
   2055 
   2056 	t.Lock()
   2057 	if !t.running {
   2058 		t.Unlock()
   2059 		return
   2060 	}
   2061 	t.running = false
   2062 	stopQ := t.stopQ
   2063 	close(stopQ)
   2064 	_ = t.tty.Drain()
   2065 	t.Unlock()
   2066 
   2067 	t.tty.NotifyResize(nil)
   2068 	// wait for everything to shut down
   2069 	t.wg.Wait()
   2070 
   2071 	// shutdown the screen and disable special modes (e.g. mouse and bracketed paste)
   2072 	ti := t.ti
   2073 	t.cells.Resize(0, 0)
   2074 	t.TPuts(ti.ShowCursor)
   2075 	if t.cursorStyles != nil && t.cursorStyle != CursorStyleDefault {
   2076 		t.TPuts(t.cursorStyles[CursorStyleDefault])
   2077 	}
   2078 	if t.cursorFg != "" && t.cursorColor.Valid() {
   2079 		t.TPuts(t.cursorFg)
   2080 	}
   2081 	t.TPuts(ti.ResetFgBg)
   2082 	t.TPuts(ti.AttrOff)
   2083 	t.TPuts(ti.ExitKeypad)
   2084 	t.TPuts(ti.EnableAutoMargin)
   2085 	if os.Getenv("TCELL_ALTSCREEN") != "disable" {
   2086 		if t.restoreTitle != "" {
   2087 			t.TPuts(t.restoreTitle)
   2088 		}
   2089 		t.TPuts(ti.Clear) // only needed if ExitCA is empty
   2090 		t.TPuts(ti.ExitCA)
   2091 	}
   2092 	t.enableMouse(0)
   2093 	t.enablePasting(false)
   2094 	t.disableFocusReporting()
   2095 
   2096 	_ = t.tty.Stop()
   2097 }
   2098 
   2099 // Beep emits a beep to the terminal.
   2100 func (t *tScreen) Beep() error {
   2101 	t.writeString(string(byte(7)))
   2102 	return nil
   2103 }
   2104 
   2105 // finalize is used to at application shutdown, and restores the terminal
   2106 // to it's initial state.  It should not be called more than once.
   2107 func (t *tScreen) finalize() {
   2108 	t.disengage()
   2109 	_ = t.tty.Close()
   2110 }
   2111 
   2112 func (t *tScreen) StopQ() <-chan struct{} {
   2113 	return t.quit
   2114 }
   2115 
   2116 func (t *tScreen) EventQ() chan Event {
   2117 	return t.eventQ
   2118 }
   2119 
   2120 func (t *tScreen) GetCells() *CellBuffer {
   2121 	return &t.cells
   2122 }
   2123 
   2124 func (t *tScreen) SetTitle(title string) {
   2125 	t.Lock()
   2126 	t.title = title
   2127 	if t.setTitle != "" && t.running {
   2128 		t.TPuts(t.ti.TParm(t.setTitle, title))
   2129 	}
   2130 	t.Unlock()
   2131 }
   2132 
   2133 func (t *tScreen) SetClipboard(data []byte) {
   2134 	// Post binary data to the system clipboard.  It might be UTF-8, it might not be.
   2135 	t.Lock()
   2136 	if t.setClipboard != "" {
   2137 		encoded := base64.StdEncoding.EncodeToString(data)
   2138 		t.TPuts(t.ti.TParm(t.setClipboard, encoded))
   2139 	}
   2140 	t.Unlock()
   2141 }
   2142 
   2143 func (t *tScreen) GetClipboard() {
   2144 	t.Lock()
   2145 	if t.setClipboard != "" {
   2146 		t.TPuts(t.ti.TParm(t.setClipboard, "?"))
   2147 	}
   2148 	t.Unlock()
   2149 }