1// Copyright 2021 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 15package tcell 16 17import ( 18 "bytes" 19 "errors" 20 "io" 21 "os" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 "unicode/utf8" 27 28 "golang.org/x/term" 29 "golang.org/x/text/transform" 30 31 "github.com/gdamore/tcell/v2/terminfo" 32 33 // import the stock terminals 34 _ "github.com/gdamore/tcell/v2/terminfo/base" 35) 36 37// NewTerminfoScreen returns a Screen that uses the stock TTY interface 38// and POSIX terminal control, combined with a terminfo description taken from 39// the $TERM environment variable. It returns an error if the terminal 40// is not supported for any reason. 41// 42// For terminals that do not support dynamic resize events, the $LINES 43// $COLUMNS environment variables can be set to the actual window size, 44// otherwise defaults taken from the terminal database are used. 45func NewTerminfoScreen() (Screen, error) { 46 return NewTerminfoScreenFromTty(nil) 47} 48 49// NewTerminfoScreenFromTty returns a Screen using a custom Tty implementation. 50// If the passed in tty is nil, then a reasonable default (typically /dev/tty) 51// is presumed, at least on UNIX hosts. (Windows hosts will typically fail this 52// call altogether.) 53func NewTerminfoScreenFromTty(tty Tty) (Screen, error) { 54 ti, e := terminfo.LookupTerminfo(os.Getenv("TERM")) 55 if e != nil { 56 ti, e = loadDynamicTerminfo(os.Getenv("TERM")) 57 if e != nil { 58 return nil, e 59 } 60 terminfo.AddTerminfo(ti) 61 } 62 t := &tScreen{ti: ti, tty: tty} 63 64 t.keyexist = make(map[Key]bool) 65 t.keycodes = make(map[string]*tKeyCode) 66 if len(ti.Mouse) > 0 { 67 t.mouse = []byte(ti.Mouse) 68 } 69 t.prepareKeys() 70 t.buildAcsMap() 71 t.resizeQ = make(chan bool, 1) 72 t.fallback = make(map[rune]string) 73 for k, v := range RuneFallbacks { 74 t.fallback[k] = v 75 } 76 77 return t, nil 78} 79 80// tKeyCode represents a combination of a key code and modifiers. 81type tKeyCode struct { 82 key Key 83 mod ModMask 84} 85 86// tScreen represents a screen backed by a terminfo implementation. 87type tScreen struct { 88 ti *terminfo.Terminfo 89 tty Tty 90 h int 91 w int 92 fini bool 93 cells CellBuffer 94 buffering bool // true if we are collecting writes to buf instead of sending directly to out 95 buf bytes.Buffer 96 curstyle Style 97 style Style 98 evch chan Event 99 resizeQ chan bool 100 quit chan struct{} 101 keyexist map[Key]bool 102 keycodes map[string]*tKeyCode 103 keychan chan []byte 104 keytimer *time.Timer 105 keyexpire time.Time 106 cx int 107 cy int 108 mouse []byte 109 clear bool 110 cursorx int 111 cursory int 112 wasbtn bool 113 acs map[rune]string 114 charset string 115 encoder transform.Transformer 116 decoder transform.Transformer 117 fallback map[rune]string 118 colors map[Color]Color 119 palette []Color 120 truecolor bool 121 escaped bool 122 buttondn bool 123 finiOnce sync.Once 124 enablePaste string 125 disablePaste string 126 saved *term.State 127 stopQ chan struct{} 128 running bool 129 wg sync.WaitGroup 130 mouseFlags MouseFlags 131 pasteEnabled bool 132 133 sync.Mutex 134} 135 136func (t *tScreen) Init() error { 137 if e := t.initialize(); e != nil { 138 return e 139 } 140 141 t.evch = make(chan Event, 10) 142 t.keychan = make(chan []byte, 10) 143 t.keytimer = time.NewTimer(time.Millisecond * 50) 144 t.charset = "UTF-8" 145 146 t.charset = getCharset() 147 if enc := GetEncoding(t.charset); enc != nil { 148 t.encoder = enc.NewEncoder() 149 t.decoder = enc.NewDecoder() 150 } else { 151 return ErrNoCharset 152 } 153 ti := t.ti 154 155 // environment overrides 156 w := ti.Columns 157 h := ti.Lines 158 if i, _ := strconv.Atoi(os.Getenv("LINES")); i != 0 { 159 h = i 160 } 161 if i, _ := strconv.Atoi(os.Getenv("COLUMNS")); i != 0 { 162 w = i 163 } 164 if t.ti.SetFgBgRGB != "" || t.ti.SetFgRGB != "" || t.ti.SetBgRGB != "" { 165 t.truecolor = true 166 } 167 // A user who wants to have his themes honored can 168 // set this environment variable. 169 if os.Getenv("TCELL_TRUECOLOR") == "disable" { 170 t.truecolor = false 171 } 172 t.colors = make(map[Color]Color) 173 t.palette = make([]Color, t.nColors()) 174 for i := 0; i < t.nColors(); i++ { 175 t.palette[i] = Color(i) | ColorValid 176 // identity map for our builtin colors 177 t.colors[Color(i)|ColorValid] = Color(i) | ColorValid 178 } 179 180 t.quit = make(chan struct{}) 181 182 t.Lock() 183 t.cx = -1 184 t.cy = -1 185 t.style = StyleDefault 186 t.cells.Resize(w, h) 187 t.cursorx = -1 188 t.cursory = -1 189 t.resize() 190 t.Unlock() 191 192 if err := t.engage(); err != nil { 193 return err 194 } 195 196 return nil 197} 198 199func (t *tScreen) prepareKeyMod(key Key, mod ModMask, val string) { 200 if val != "" { 201 // Do not override codes that already exist 202 if _, exist := t.keycodes[val]; !exist { 203 t.keyexist[key] = true 204 t.keycodes[val] = &tKeyCode{key: key, mod: mod} 205 } 206 } 207} 208 209func (t *tScreen) prepareKeyModReplace(key Key, replace Key, mod ModMask, val string) { 210 if val != "" { 211 // Do not override codes that already exist 212 if old, exist := t.keycodes[val]; !exist || old.key == replace { 213 t.keyexist[key] = true 214 t.keycodes[val] = &tKeyCode{key: key, mod: mod} 215 } 216 } 217} 218 219func (t *tScreen) prepareKeyModXTerm(key Key, val string) { 220 221 if strings.HasPrefix(val, "\x1b[") && strings.HasSuffix(val, "~") { 222 223 // Drop the trailing ~ 224 val = val[:len(val)-1] 225 226 // These suffixes are calculated assuming Xterm style modifier suffixes. 227 // Please see https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf for 228 // more information (specifically "PC-Style Function Keys"). 229 t.prepareKeyModReplace(key, key+12, ModShift, val+";2~") 230 t.prepareKeyModReplace(key, key+48, ModAlt, val+";3~") 231 t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, val+";4~") 232 t.prepareKeyModReplace(key, key+24, ModCtrl, val+";5~") 233 t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, val+";6~") 234 t.prepareKeyMod(key, ModAlt|ModCtrl, val+";7~") 235 t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, val+";8~") 236 t.prepareKeyMod(key, ModMeta, val+";9~") 237 t.prepareKeyMod(key, ModMeta|ModShift, val+";10~") 238 t.prepareKeyMod(key, ModMeta|ModAlt, val+";11~") 239 t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, val+";12~") 240 t.prepareKeyMod(key, ModMeta|ModCtrl, val+";13~") 241 t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, val+";14~") 242 t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, val+";15~") 243 t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, val+";16~") 244 } else if strings.HasPrefix(val, "\x1bO") && len(val) == 3 { 245 val = val[2:] 246 t.prepareKeyModReplace(key, key+12, ModShift, "\x1b[1;2"+val) 247 t.prepareKeyModReplace(key, key+48, ModAlt, "\x1b[1;3"+val) 248 t.prepareKeyModReplace(key, key+24, ModCtrl, "\x1b[1;5"+val) 249 t.prepareKeyModReplace(key, key+36, ModCtrl|ModShift, "\x1b[1;6"+val) 250 t.prepareKeyModReplace(key, key+60, ModAlt|ModShift, "\x1b[1;4"+val) 251 t.prepareKeyMod(key, ModAlt|ModCtrl, "\x1b[1;7"+val) 252 t.prepareKeyMod(key, ModShift|ModAlt|ModCtrl, "\x1b[1;8"+val) 253 t.prepareKeyMod(key, ModMeta, "\x1b[1;9"+val) 254 t.prepareKeyMod(key, ModMeta|ModShift, "\x1b[1;10"+val) 255 t.prepareKeyMod(key, ModMeta|ModAlt, "\x1b[1;11"+val) 256 t.prepareKeyMod(key, ModMeta|ModAlt|ModShift, "\x1b[1;12"+val) 257 t.prepareKeyMod(key, ModMeta|ModCtrl, "\x1b[1;13"+val) 258 t.prepareKeyMod(key, ModMeta|ModCtrl|ModShift, "\x1b[1;14"+val) 259 t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt, "\x1b[1;15"+val) 260 t.prepareKeyMod(key, ModMeta|ModCtrl|ModAlt|ModShift, "\x1b[1;16"+val) 261 } 262} 263 264func (t *tScreen) prepareXtermModifiers() { 265 if t.ti.Modifiers != terminfo.ModifiersXTerm { 266 return 267 } 268 t.prepareKeyModXTerm(KeyRight, t.ti.KeyRight) 269 t.prepareKeyModXTerm(KeyLeft, t.ti.KeyLeft) 270 t.prepareKeyModXTerm(KeyUp, t.ti.KeyUp) 271 t.prepareKeyModXTerm(KeyDown, t.ti.KeyDown) 272 t.prepareKeyModXTerm(KeyInsert, t.ti.KeyInsert) 273 t.prepareKeyModXTerm(KeyDelete, t.ti.KeyDelete) 274 t.prepareKeyModXTerm(KeyPgUp, t.ti.KeyPgUp) 275 t.prepareKeyModXTerm(KeyPgDn, t.ti.KeyPgDn) 276 t.prepareKeyModXTerm(KeyHome, t.ti.KeyHome) 277 t.prepareKeyModXTerm(KeyEnd, t.ti.KeyEnd) 278 t.prepareKeyModXTerm(KeyF1, t.ti.KeyF1) 279 t.prepareKeyModXTerm(KeyF2, t.ti.KeyF2) 280 t.prepareKeyModXTerm(KeyF3, t.ti.KeyF3) 281 t.prepareKeyModXTerm(KeyF4, t.ti.KeyF4) 282 t.prepareKeyModXTerm(KeyF5, t.ti.KeyF5) 283 t.prepareKeyModXTerm(KeyF6, t.ti.KeyF6) 284 t.prepareKeyModXTerm(KeyF7, t.ti.KeyF7) 285 t.prepareKeyModXTerm(KeyF8, t.ti.KeyF8) 286 t.prepareKeyModXTerm(KeyF9, t.ti.KeyF9) 287 t.prepareKeyModXTerm(KeyF10, t.ti.KeyF10) 288 t.prepareKeyModXTerm(KeyF11, t.ti.KeyF11) 289 t.prepareKeyModXTerm(KeyF12, t.ti.KeyF12) 290} 291 292func (t *tScreen) prepareBracketedPaste() { 293 // Another workaround for lack of reporting in terminfo. 294 // We assume if the terminal has a mouse entry, that it 295 // offers bracketed paste. But we allow specific overrides 296 // via our terminal database. 297 if t.ti.EnablePaste != "" { 298 t.enablePaste = t.ti.EnablePaste 299 t.disablePaste = t.ti.DisablePaste 300 t.prepareKey(keyPasteStart, t.ti.PasteStart) 301 t.prepareKey(keyPasteEnd, t.ti.PasteEnd) 302 } else if t.ti.Mouse != "" { 303 t.enablePaste = "\x1b[?2004h" 304 t.disablePaste = "\x1b[?2004l" 305 t.prepareKey(keyPasteStart, "\x1b[200~") 306 t.prepareKey(keyPasteEnd, "\x1b[201~") 307 } 308} 309 310func (t *tScreen) prepareKey(key Key, val string) { 311 t.prepareKeyMod(key, ModNone, val) 312} 313 314func (t *tScreen) prepareKeys() { 315 ti := t.ti 316 t.prepareKey(KeyBackspace, ti.KeyBackspace) 317 t.prepareKey(KeyF1, ti.KeyF1) 318 t.prepareKey(KeyF2, ti.KeyF2) 319 t.prepareKey(KeyF3, ti.KeyF3) 320 t.prepareKey(KeyF4, ti.KeyF4) 321 t.prepareKey(KeyF5, ti.KeyF5) 322 t.prepareKey(KeyF6, ti.KeyF6) 323 t.prepareKey(KeyF7, ti.KeyF7) 324 t.prepareKey(KeyF8, ti.KeyF8) 325 t.prepareKey(KeyF9, ti.KeyF9) 326 t.prepareKey(KeyF10, ti.KeyF10) 327 t.prepareKey(KeyF11, ti.KeyF11) 328 t.prepareKey(KeyF12, ti.KeyF12) 329 t.prepareKey(KeyF13, ti.KeyF13) 330 t.prepareKey(KeyF14, ti.KeyF14) 331 t.prepareKey(KeyF15, ti.KeyF15) 332 t.prepareKey(KeyF16, ti.KeyF16) 333 t.prepareKey(KeyF17, ti.KeyF17) 334 t.prepareKey(KeyF18, ti.KeyF18) 335 t.prepareKey(KeyF19, ti.KeyF19) 336 t.prepareKey(KeyF20, ti.KeyF20) 337 t.prepareKey(KeyF21, ti.KeyF21) 338 t.prepareKey(KeyF22, ti.KeyF22) 339 t.prepareKey(KeyF23, ti.KeyF23) 340 t.prepareKey(KeyF24, ti.KeyF24) 341 t.prepareKey(KeyF25, ti.KeyF25) 342 t.prepareKey(KeyF26, ti.KeyF26) 343 t.prepareKey(KeyF27, ti.KeyF27) 344 t.prepareKey(KeyF28, ti.KeyF28) 345 t.prepareKey(KeyF29, ti.KeyF29) 346 t.prepareKey(KeyF30, ti.KeyF30) 347 t.prepareKey(KeyF31, ti.KeyF31) 348 t.prepareKey(KeyF32, ti.KeyF32) 349 t.prepareKey(KeyF33, ti.KeyF33) 350 t.prepareKey(KeyF34, ti.KeyF34) 351 t.prepareKey(KeyF35, ti.KeyF35) 352 t.prepareKey(KeyF36, ti.KeyF36) 353 t.prepareKey(KeyF37, ti.KeyF37) 354 t.prepareKey(KeyF38, ti.KeyF38) 355 t.prepareKey(KeyF39, ti.KeyF39) 356 t.prepareKey(KeyF40, ti.KeyF40) 357 t.prepareKey(KeyF41, ti.KeyF41) 358 t.prepareKey(KeyF42, ti.KeyF42) 359 t.prepareKey(KeyF43, ti.KeyF43) 360 t.prepareKey(KeyF44, ti.KeyF44) 361 t.prepareKey(KeyF45, ti.KeyF45) 362 t.prepareKey(KeyF46, ti.KeyF46) 363 t.prepareKey(KeyF47, ti.KeyF47) 364 t.prepareKey(KeyF48, ti.KeyF48) 365 t.prepareKey(KeyF49, ti.KeyF49) 366 t.prepareKey(KeyF50, ti.KeyF50) 367 t.prepareKey(KeyF51, ti.KeyF51) 368 t.prepareKey(KeyF52, ti.KeyF52) 369 t.prepareKey(KeyF53, ti.KeyF53) 370 t.prepareKey(KeyF54, ti.KeyF54) 371 t.prepareKey(KeyF55, ti.KeyF55) 372 t.prepareKey(KeyF56, ti.KeyF56) 373 t.prepareKey(KeyF57, ti.KeyF57) 374 t.prepareKey(KeyF58, ti.KeyF58) 375 t.prepareKey(KeyF59, ti.KeyF59) 376 t.prepareKey(KeyF60, ti.KeyF60) 377 t.prepareKey(KeyF61, ti.KeyF61) 378 t.prepareKey(KeyF62, ti.KeyF62) 379 t.prepareKey(KeyF63, ti.KeyF63) 380 t.prepareKey(KeyF64, ti.KeyF64) 381 t.prepareKey(KeyInsert, ti.KeyInsert) 382 t.prepareKey(KeyDelete, ti.KeyDelete) 383 t.prepareKey(KeyHome, ti.KeyHome) 384 t.prepareKey(KeyEnd, ti.KeyEnd) 385 t.prepareKey(KeyUp, ti.KeyUp) 386 t.prepareKey(KeyDown, ti.KeyDown) 387 t.prepareKey(KeyLeft, ti.KeyLeft) 388 t.prepareKey(KeyRight, ti.KeyRight) 389 t.prepareKey(KeyPgUp, ti.KeyPgUp) 390 t.prepareKey(KeyPgDn, ti.KeyPgDn) 391 t.prepareKey(KeyHelp, ti.KeyHelp) 392 t.prepareKey(KeyPrint, ti.KeyPrint) 393 t.prepareKey(KeyCancel, ti.KeyCancel) 394 t.prepareKey(KeyExit, ti.KeyExit) 395 t.prepareKey(KeyBacktab, ti.KeyBacktab) 396 397 t.prepareKeyMod(KeyRight, ModShift, ti.KeyShfRight) 398 t.prepareKeyMod(KeyLeft, ModShift, ti.KeyShfLeft) 399 t.prepareKeyMod(KeyUp, ModShift, ti.KeyShfUp) 400 t.prepareKeyMod(KeyDown, ModShift, ti.KeyShfDown) 401 t.prepareKeyMod(KeyHome, ModShift, ti.KeyShfHome) 402 t.prepareKeyMod(KeyEnd, ModShift, ti.KeyShfEnd) 403 t.prepareKeyMod(KeyPgUp, ModShift, ti.KeyShfPgUp) 404 t.prepareKeyMod(KeyPgDn, ModShift, ti.KeyShfPgDn) 405 406 t.prepareKeyMod(KeyRight, ModCtrl, ti.KeyCtrlRight) 407 t.prepareKeyMod(KeyLeft, ModCtrl, ti.KeyCtrlLeft) 408 t.prepareKeyMod(KeyUp, ModCtrl, ti.KeyCtrlUp) 409 t.prepareKeyMod(KeyDown, ModCtrl, ti.KeyCtrlDown) 410 t.prepareKeyMod(KeyHome, ModCtrl, ti.KeyCtrlHome) 411 t.prepareKeyMod(KeyEnd, ModCtrl, ti.KeyCtrlEnd) 412 413 // Sadly, xterm handling of keycodes is somewhat erratic. In 414 // particular, different codes are sent depending on application 415 // mode is in use or not, and the entries for many of these are 416 // simply absent from terminfo on many systems. So we insert 417 // a number of escape sequences if they are not already used, in 418 // order to have the widest correct usage. Note that prepareKey 419 // will not inject codes if the escape sequence is already known. 420 // We also only do this for terminals that have the application 421 // mode present. 422 423 // Cursor mode 424 if ti.EnterKeypad != "" { 425 t.prepareKey(KeyUp, "\x1b[A") 426 t.prepareKey(KeyDown, "\x1b[B") 427 t.prepareKey(KeyRight, "\x1b[C") 428 t.prepareKey(KeyLeft, "\x1b[D") 429 t.prepareKey(KeyEnd, "\x1b[F") 430 t.prepareKey(KeyHome, "\x1b[H") 431 t.prepareKey(KeyDelete, "\x1b[3~") 432 t.prepareKey(KeyHome, "\x1b[1~") 433 t.prepareKey(KeyEnd, "\x1b[4~") 434 t.prepareKey(KeyPgUp, "\x1b[5~") 435 t.prepareKey(KeyPgDn, "\x1b[6~") 436 437 // Application mode 438 t.prepareKey(KeyUp, "\x1bOA") 439 t.prepareKey(KeyDown, "\x1bOB") 440 t.prepareKey(KeyRight, "\x1bOC") 441 t.prepareKey(KeyLeft, "\x1bOD") 442 t.prepareKey(KeyHome, "\x1bOH") 443 } 444 445 t.prepareKey(keyPasteStart, ti.PasteStart) 446 t.prepareKey(keyPasteEnd, ti.PasteEnd) 447 t.prepareXtermModifiers() 448 t.prepareBracketedPaste() 449 450outer: 451 // Add key mappings for control keys. 452 for i := 0; i < ' '; i++ { 453 // Do not insert direct key codes for ambiguous keys. 454 // For example, ESC is used for lots of other keys, so 455 // when parsing this we don't want to fast path handling 456 // of it, but instead wait a bit before parsing it as in 457 // isolation. 458 for esc := range t.keycodes { 459 if []byte(esc)[0] == byte(i) { 460 continue outer 461 } 462 } 463 464 t.keyexist[Key(i)] = true 465 466 mod := ModCtrl 467 switch Key(i) { 468 case KeyBS, KeyTAB, KeyESC, KeyCR: 469 // directly type-able- no control sequence 470 mod = ModNone 471 } 472 t.keycodes[string(rune(i))] = &tKeyCode{key: Key(i), mod: mod} 473 } 474} 475 476func (t *tScreen) Fini() { 477 t.finiOnce.Do(t.finish) 478} 479 480func (t *tScreen) finish() { 481 close(t.quit) 482 t.finalize() 483} 484 485func (t *tScreen) SetStyle(style Style) { 486 t.Lock() 487 if !t.fini { 488 t.style = style 489 } 490 t.Unlock() 491} 492 493func (t *tScreen) Clear() { 494 t.Fill(' ', t.style) 495} 496 497func (t *tScreen) Fill(r rune, style Style) { 498 t.Lock() 499 if !t.fini { 500 t.cells.Fill(r, style) 501 } 502 t.Unlock() 503} 504 505func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) { 506 t.Lock() 507 if !t.fini { 508 t.cells.SetContent(x, y, mainc, combc, style) 509 } 510 t.Unlock() 511} 512 513func (t *tScreen) GetContent(x, y int) (rune, []rune, Style, int) { 514 t.Lock() 515 mainc, combc, style, width := t.cells.GetContent(x, y) 516 t.Unlock() 517 return mainc, combc, style, width 518} 519 520func (t *tScreen) SetCell(x, y int, style Style, ch ...rune) { 521 if len(ch) > 0 { 522 t.SetContent(x, y, ch[0], ch[1:], style) 523 } else { 524 t.SetContent(x, y, ' ', nil, style) 525 } 526} 527 528func (t *tScreen) encodeRune(r rune, buf []byte) []byte { 529 530 nb := make([]byte, 6) 531 ob := make([]byte, 6) 532 num := utf8.EncodeRune(ob, r) 533 ob = ob[:num] 534 dst := 0 535 var err error 536 if enc := t.encoder; enc != nil { 537 enc.Reset() 538 dst, _, err = enc.Transform(nb, ob, true) 539 } 540 if err != nil || dst == 0 || nb[0] == '\x1a' { 541 // Combining characters are elided 542 if len(buf) == 0 { 543 if acs, ok := t.acs[r]; ok { 544 buf = append(buf, []byte(acs)...) 545 } else if fb, ok := t.fallback[r]; ok { 546 buf = append(buf, []byte(fb)...) 547 } else { 548 buf = append(buf, '?') 549 } 550 } 551 } else { 552 buf = append(buf, nb[:dst]...) 553 } 554 555 return buf 556} 557 558func (t *tScreen) sendFgBg(fg Color, bg Color) { 559 ti := t.ti 560 if ti.Colors == 0 { 561 return 562 } 563 if fg == ColorReset || bg == ColorReset { 564 t.TPuts(ti.ResetFgBg) 565 } 566 if t.truecolor { 567 if ti.SetFgBgRGB != "" && fg.IsRGB() && bg.IsRGB() { 568 r1, g1, b1 := fg.RGB() 569 r2, g2, b2 := bg.RGB() 570 t.TPuts(ti.TParm(ti.SetFgBgRGB, 571 int(r1), int(g1), int(b1), 572 int(r2), int(g2), int(b2))) 573 return 574 } 575 576 if fg.IsRGB() && ti.SetFgRGB != "" { 577 r, g, b := fg.RGB() 578 t.TPuts(ti.TParm(ti.SetFgRGB, int(r), int(g), int(b))) 579 fg = ColorDefault 580 } 581 582 if bg.IsRGB() && ti.SetBgRGB != "" { 583 r, g, b := bg.RGB() 584 t.TPuts(ti.TParm(ti.SetBgRGB, 585 int(r), int(g), int(b))) 586 bg = ColorDefault 587 } 588 } 589 590 if fg.Valid() { 591 if v, ok := t.colors[fg]; ok { 592 fg = v 593 } else { 594 v = FindColor(fg, t.palette) 595 t.colors[fg] = v 596 fg = v 597 } 598 } 599 600 if bg.Valid() { 601 if v, ok := t.colors[bg]; ok { 602 bg = v 603 } else { 604 v = FindColor(bg, t.palette) 605 t.colors[bg] = v 606 bg = v 607 } 608 } 609 610 if fg.Valid() && bg.Valid() && ti.SetFgBg != "" { 611 t.TPuts(ti.TParm(ti.SetFgBg, int(fg&0xff), int(bg&0xff))) 612 } else { 613 if fg.Valid() && ti.SetFg != "" { 614 t.TPuts(ti.TParm(ti.SetFg, int(fg&0xff))) 615 } 616 if bg.Valid() && ti.SetBg != "" { 617 t.TPuts(ti.TParm(ti.SetBg, int(bg&0xff))) 618 } 619 } 620} 621 622func (t *tScreen) drawCell(x, y int) int { 623 624 ti := t.ti 625 626 mainc, combc, style, width := t.cells.GetContent(x, y) 627 if !t.cells.Dirty(x, y) { 628 return width 629 } 630 631 if y == t.h-1 && x == t.w-1 && t.ti.AutoMargin && ti.InsertChar != "" { 632 // our solution is somewhat goofy. 633 // we write to the second to the last cell what we want in the last cell, then we 634 // insert a character at that 2nd to last position to shift the last column into 635 // place, then we rewrite that 2nd to last cell. Old terminals suck. 636 t.TPuts(ti.TGoto(x-1, y)) 637 defer func() { 638 t.TPuts(ti.TGoto(x-1, y)) 639 t.TPuts(ti.InsertChar) 640 t.cy = y 641 t.cx = x - 1 642 t.cells.SetDirty(x-1, y, true) 643 _ = t.drawCell(x-1, y) 644 t.TPuts(t.ti.TGoto(0, 0)) 645 t.cy = 0 646 t.cx = 0 647 }() 648 } else if t.cy != y || t.cx != x { 649 t.TPuts(ti.TGoto(x, y)) 650 t.cx = x 651 t.cy = y 652 } 653 654 if style == StyleDefault { 655 style = t.style 656 } 657 if style != t.curstyle { 658 fg, bg, attrs := style.Decompose() 659 660 t.TPuts(ti.AttrOff) 661 662 t.sendFgBg(fg, bg) 663 if attrs&AttrBold != 0 { 664 t.TPuts(ti.Bold) 665 } 666 if attrs&AttrUnderline != 0 { 667 t.TPuts(ti.Underline) 668 } 669 if attrs&AttrReverse != 0 { 670 t.TPuts(ti.Reverse) 671 } 672 if attrs&AttrBlink != 0 { 673 t.TPuts(ti.Blink) 674 } 675 if attrs&AttrDim != 0 { 676 t.TPuts(ti.Dim) 677 } 678 if attrs&AttrItalic != 0 { 679 t.TPuts(ti.Italic) 680 } 681 if attrs&AttrStrikeThrough != 0 { 682 t.TPuts(ti.StrikeThrough) 683 } 684 t.curstyle = style 685 } 686 // now emit runes - taking care to not overrun width with a 687 // wide character, and to ensure that we emit exactly one regular 688 // character followed up by any residual combing characters 689 690 if width < 1 { 691 width = 1 692 } 693 694 var str string 695 696 buf := make([]byte, 0, 6) 697 698 buf = t.encodeRune(mainc, buf) 699 for _, r := range combc { 700 buf = t.encodeRune(r, buf) 701 } 702 703 str = string(buf) 704 if width > 1 && str == "?" { 705 // No FullWidth character support 706 str = "? " 707 t.cx = -1 708 } 709 710 if x > t.w-width { 711 // too wide to fit; emit a single space instead 712 width = 1 713 str = " " 714 } 715 t.writeString(str) 716 t.cx += width 717 t.cells.SetDirty(x, y, false) 718 if width > 1 { 719 t.cx = -1 720 } 721 722 return width 723} 724 725func (t *tScreen) ShowCursor(x, y int) { 726 t.Lock() 727 t.cursorx = x 728 t.cursory = y 729 t.Unlock() 730} 731 732func (t *tScreen) HideCursor() { 733 t.ShowCursor(-1, -1) 734} 735 736func (t *tScreen) showCursor() { 737 738 x, y := t.cursorx, t.cursory 739 w, h := t.cells.Size() 740 if x < 0 || y < 0 || x >= w || y >= h { 741 t.hideCursor() 742 return 743 } 744 t.TPuts(t.ti.TGoto(x, y)) 745 t.TPuts(t.ti.ShowCursor) 746 t.cx = x 747 t.cy = y 748} 749 750// writeString sends a string to the terminal. The string is sent as-is and 751// this function does not expand inline padding indications (of the form 752// $<[delay]> where [delay] is msec). In order to have these expanded, use 753// TPuts. If the screen is "buffering", the string is collected in a buffer, 754// with the intention that the entire buffer be sent to the terminal in one 755// write operation at some point later. 756func (t *tScreen) writeString(s string) { 757 if t.buffering { 758 _, _ = io.WriteString(&t.buf, s) 759 } else { 760 _, _ = io.WriteString(t.tty, s) 761 } 762} 763 764func (t *tScreen) TPuts(s string) { 765 if t.buffering { 766 t.ti.TPuts(&t.buf, s) 767 } else { 768 t.ti.TPuts(t.tty, s) 769 } 770} 771 772func (t *tScreen) Show() { 773 t.Lock() 774 if !t.fini { 775 t.resize() 776 t.draw() 777 } 778 t.Unlock() 779} 780 781func (t *tScreen) clearScreen() { 782 fg, bg, _ := t.style.Decompose() 783 t.sendFgBg(fg, bg) 784 t.TPuts(t.ti.Clear) 785 t.clear = false 786} 787 788func (t *tScreen) hideCursor() { 789 // does not update cursor position 790 if t.ti.HideCursor != "" { 791 t.TPuts(t.ti.HideCursor) 792 } else { 793 // No way to hide cursor, stick it 794 // at bottom right of screen 795 t.cx, t.cy = t.cells.Size() 796 t.TPuts(t.ti.TGoto(t.cx, t.cy)) 797 } 798} 799 800func (t *tScreen) draw() { 801 // clobber cursor position, because we're gonna change it all 802 t.cx = -1 803 t.cy = -1 804 805 t.buf.Reset() 806 t.buffering = true 807 defer func() { 808 t.buffering = false 809 }() 810 811 // hide the cursor while we move stuff around 812 t.hideCursor() 813 814 if t.clear { 815 t.clearScreen() 816 } 817 818 for y := 0; y < t.h; y++ { 819 for x := 0; x < t.w; x++ { 820 width := t.drawCell(x, y) 821 if width > 1 { 822 if x+1 < t.w { 823 // this is necessary so that if we ever 824 // go back to drawing that cell, we 825 // actually will *draw* it. 826 t.cells.SetDirty(x+1, y, true) 827 } 828 } 829 x += width - 1 830 } 831 } 832 833 // restore the cursor 834 t.showCursor() 835 836 _, _ = t.buf.WriteTo(t.tty) 837} 838 839func (t *tScreen) EnableMouse(flags ...MouseFlags) { 840 var f MouseFlags 841 flagsPresent := false 842 for _, flag := range flags { 843 f |= flag 844 flagsPresent = true 845 } 846 if !flagsPresent { 847 f = MouseMotionEvents 848 } 849 850 t.Lock() 851 t.mouseFlags = f 852 t.enableMouse(f) 853 t.Unlock() 854} 855 856func (t *tScreen) enableMouse(f MouseFlags) { 857 // Rather than using terminfo to find mouse escape sequences, we rely on the fact that 858 // pretty much *every* terminal that supports mouse tracking follows the 859 // XTerm standards (the modern ones). 860 if len(t.mouse) != 0 { 861 // start by disabling all tracking. 862 t.TPuts("\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l") 863 if f&MouseMotionEvents != 0 { 864 t.TPuts("\x1b[?1003h\x1b[?1006h") 865 } else if f&MouseDragEvents != 0 { 866 t.TPuts("\x1b[?1002h\x1b[?1006h") 867 } else if f&MouseButtonEvents != 0 { 868 t.TPuts("\x1b[?1000h\x1b[?1006h") 869 } 870 } 871} 872 873func (t *tScreen) DisableMouse() { 874 t.Lock() 875 t.mouseFlags = 0 876 t.enableMouse(0) 877 t.Unlock() 878} 879 880func (t *tScreen) EnablePaste() { 881 t.Lock() 882 t.pasteEnabled = true 883 t.enablePasting(true) 884 t.Unlock() 885} 886 887func (t *tScreen) DisablePaste() { 888 t.Lock() 889 t.pasteEnabled = false 890 t.enablePasting(false) 891 t.Unlock() 892} 893 894func (t *tScreen) enablePasting(on bool) { 895 var s string 896 if on { 897 s = t.enablePaste 898 } else { 899 s = t.disablePaste 900 } 901 if s != "" { 902 t.TPuts(s) 903 } 904} 905 906func (t *tScreen) Size() (int, int) { 907 t.Lock() 908 w, h := t.w, t.h 909 t.Unlock() 910 return w, h 911} 912 913func (t *tScreen) resize() { 914 if w, h, e := t.tty.WindowSize(); e == nil { 915 if w != t.w || h != t.h { 916 t.cx = -1 917 t.cy = -1 918 919 t.cells.Resize(w, h) 920 t.cells.Invalidate() 921 t.h = h 922 t.w = w 923 ev := NewEventResize(w, h) 924 _ = t.PostEvent(ev) 925 } 926 } 927} 928 929func (t *tScreen) Colors() int { 930 // this doesn't change, no need for lock 931 if t.truecolor { 932 return 1 << 24 933 } 934 return t.ti.Colors 935} 936 937// nColors returns the size of the built-in palette. 938// This is distinct from Colors(), as it will generally 939// always be a small number. (<= 256) 940func (t *tScreen) nColors() int { 941 return t.ti.Colors 942} 943 944func (t *tScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) { 945 defer close(ch) 946 for { 947 select { 948 case <-quit: 949 return 950 case <-t.quit: 951 return 952 case ev := <-t.evch: 953 select { 954 case <-quit: 955 return 956 case <-t.quit: 957 return 958 case ch <- ev: 959 } 960 } 961 } 962} 963 964func (t *tScreen) PollEvent() Event { 965 select { 966 case <-t.quit: 967 return nil 968 case ev := <-t.evch: 969 return ev 970 } 971} 972 973func (t *tScreen) HasPendingEvent() bool { 974 return len(t.evch) > 0 975} 976 977// vtACSNames is a map of bytes defined by terminfo that are used in 978// the terminals Alternate Character Set to represent other glyphs. 979// For example, the upper left corner of the box drawing set can be 980// displayed by printing "l" while in the alternate character set. 981// Its not quite that simple, since the "l" is the terminfo name, 982// and it may be necessary to use a different character based on 983// the terminal implementation (or the terminal may lack support for 984// this altogether). See buildAcsMap below for detail. 985var vtACSNames = map[byte]rune{ 986 '+': RuneRArrow, 987 ',': RuneLArrow, 988 '-': RuneUArrow, 989 '.': RuneDArrow, 990 '0': RuneBlock, 991 '`': RuneDiamond, 992 'a': RuneCkBoard, 993 'b': '␉', // VT100, Not defined by terminfo 994 'c': '␌', // VT100, Not defined by terminfo 995 'd': '␋', // VT100, Not defined by terminfo 996 'e': '␊', // VT100, Not defined by terminfo 997 'f': RuneDegree, 998 'g': RunePlMinus, 999 'h': RuneBoard, 1000 'i': RuneLantern, 1001 'j': RuneLRCorner, 1002 'k': RuneURCorner, 1003 'l': RuneULCorner, 1004 'm': RuneLLCorner, 1005 'n': RunePlus, 1006 'o': RuneS1, 1007 'p': RuneS3, 1008 'q': RuneHLine, 1009 'r': RuneS7, 1010 's': RuneS9, 1011 't': RuneLTee, 1012 'u': RuneRTee, 1013 'v': RuneBTee, 1014 'w': RuneTTee, 1015 'x': RuneVLine, 1016 'y': RuneLEqual, 1017 'z': RuneGEqual, 1018 '{': RunePi, 1019 '|': RuneNEqual, 1020 '}': RuneSterling, 1021 '~': RuneBullet, 1022} 1023 1024// buildAcsMap builds a map of characters that we translate from Unicode to 1025// alternate character encodings. To do this, we use the standard VT100 ACS 1026// maps. This is only done if the terminal lacks support for Unicode; we 1027// always prefer to emit Unicode glyphs when we are able. 1028func (t *tScreen) buildAcsMap() { 1029 acsstr := t.ti.AltChars 1030 t.acs = make(map[rune]string) 1031 for len(acsstr) > 2 { 1032 srcv := acsstr[0] 1033 dstv := string(acsstr[1]) 1034 if r, ok := vtACSNames[srcv]; ok { 1035 t.acs[r] = t.ti.EnterAcs + dstv + t.ti.ExitAcs 1036 } 1037 acsstr = acsstr[2:] 1038 } 1039} 1040 1041func (t *tScreen) PostEventWait(ev Event) { 1042 t.evch <- ev 1043} 1044 1045func (t *tScreen) PostEvent(ev Event) error { 1046 select { 1047 case t.evch <- ev: 1048 return nil 1049 default: 1050 return ErrEventQFull 1051 } 1052} 1053 1054func (t *tScreen) clip(x, y int) (int, int) { 1055 w, h := t.cells.Size() 1056 if x < 0 { 1057 x = 0 1058 } 1059 if y < 0 { 1060 y = 0 1061 } 1062 if x > w-1 { 1063 x = w - 1 1064 } 1065 if y > h-1 { 1066 y = h - 1 1067 } 1068 return x, y 1069} 1070 1071// buildMouseEvent returns an event based on the supplied coordinates and button 1072// state. Note that the screen's mouse button state is updated based on the 1073// input to this function (i.e. it mutates the receiver). 1074func (t *tScreen) buildMouseEvent(x, y, btn int) *EventMouse { 1075 1076 // XTerm mouse events only report at most one button at a time, 1077 // which may include a wheel button. Wheel motion events are 1078 // reported as single impulses, while other button events are reported 1079 // as separate press & release events. 1080 1081 button := ButtonNone 1082 mod := ModNone 1083 1084 // Mouse wheel has bit 6 set, no release events. It should be noted 1085 // that wheel events are sometimes misdelivered as mouse button events 1086 // during a click-drag, so we debounce these, considering them to be 1087 // button press events unless we see an intervening release event. 1088 switch btn & 0x43 { 1089 case 0: 1090 button = Button1 1091 t.wasbtn = true 1092 case 1: 1093 button = Button3 // Note we prefer to treat right as button 2 1094 t.wasbtn = true 1095 case 2: 1096 button = Button2 // And the middle button as button 3 1097 t.wasbtn = true 1098 case 3: 1099 button = ButtonNone 1100 t.wasbtn = false 1101 case 0x40: 1102 if !t.wasbtn { 1103 button = WheelUp 1104 } else { 1105 button = Button1 1106 } 1107 case 0x41: 1108 if !t.wasbtn { 1109 button = WheelDown 1110 } else { 1111 button = Button2 1112 } 1113 } 1114 1115 if btn&0x4 != 0 { 1116 mod |= ModShift 1117 } 1118 if btn&0x8 != 0 { 1119 mod |= ModAlt 1120 } 1121 if btn&0x10 != 0 { 1122 mod |= ModCtrl 1123 } 1124 1125 // Some terminals will report mouse coordinates outside the 1126 // screen, especially with click-drag events. Clip the coordinates 1127 // to the screen in that case. 1128 x, y = t.clip(x, y) 1129 1130 return NewEventMouse(x, y, button, mod) 1131} 1132 1133// parseSgrMouse attempts to locate an SGR mouse record at the start of the 1134// buffer. It returns true, true if it found one, and the associated bytes 1135// be removed from the buffer. It returns true, false if the buffer might 1136// contain such an event, but more bytes are necessary (partial match), and 1137// false, false if the content is definitely *not* an SGR mouse record. 1138func (t *tScreen) parseSgrMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { 1139 1140 b := buf.Bytes() 1141 1142 var x, y, btn, state int 1143 dig := false 1144 neg := false 1145 motion := false 1146 i := 0 1147 val := 0 1148 1149 for i = range b { 1150 switch b[i] { 1151 case '\x1b': 1152 if state != 0 { 1153 return false, false 1154 } 1155 state = 1 1156 1157 case '\x9b': 1158 if state != 0 { 1159 return false, false 1160 } 1161 state = 2 1162 1163 case '[': 1164 if state != 1 { 1165 return false, false 1166 } 1167 state = 2 1168 1169 case '<': 1170 if state != 2 { 1171 return false, false 1172 } 1173 val = 0 1174 dig = false 1175 neg = false 1176 state = 3 1177 1178 case '-': 1179 if state != 3 && state != 4 && state != 5 { 1180 return false, false 1181 } 1182 if dig || neg { 1183 return false, false 1184 } 1185 neg = true // stay in state 1186 1187 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': 1188 if state != 3 && state != 4 && state != 5 { 1189 return false, false 1190 } 1191 val *= 10 1192 val += int(b[i] - '0') 1193 dig = true // stay in state 1194 1195 case ';': 1196 if neg { 1197 val = -val 1198 } 1199 switch state { 1200 case 3: 1201 btn, val = val, 0 1202 neg, dig, state = false, false, 4 1203 case 4: 1204 x, val = val-1, 0 1205 neg, dig, state = false, false, 5 1206 default: 1207 return false, false 1208 } 1209 1210 case 'm', 'M': 1211 if state != 5 { 1212 return false, false 1213 } 1214 if neg { 1215 val = -val 1216 } 1217 y = val - 1 1218 1219 motion = (btn & 32) != 0 1220 btn &^= 32 1221 if b[i] == 'm' { 1222 // mouse release, clear all buttons 1223 btn |= 3 1224 btn &^= 0x40 1225 t.buttondn = false 1226 } else if motion { 1227 /* 1228 * Some broken terminals appear to send 1229 * mouse button one motion events, instead of 1230 * encoding 35 (no buttons) into these events. 1231 * We resolve these by looking for a non-motion 1232 * event first. 1233 */ 1234 if !t.buttondn { 1235 btn |= 3 1236 btn &^= 0x40 1237 } 1238 } else { 1239 t.buttondn = true 1240 } 1241 // consume the event bytes 1242 for i >= 0 { 1243 _, _ = buf.ReadByte() 1244 i-- 1245 } 1246 *evs = append(*evs, t.buildMouseEvent(x, y, btn)) 1247 return true, true 1248 } 1249 } 1250 1251 // incomplete & inconclusive at this point 1252 return true, false 1253} 1254 1255// parseXtermMouse is like parseSgrMouse, but it parses a legacy 1256// X11 mouse record. 1257func (t *tScreen) parseXtermMouse(buf *bytes.Buffer, evs *[]Event) (bool, bool) { 1258 1259 b := buf.Bytes() 1260 1261 state := 0 1262 btn := 0 1263 x := 0 1264 y := 0 1265 1266 for i := range b { 1267 switch state { 1268 case 0: 1269 switch b[i] { 1270 case '\x1b': 1271 state = 1 1272 case '\x9b': 1273 state = 2 1274 default: 1275 return false, false 1276 } 1277 case 1: 1278 if b[i] != '[' { 1279 return false, false 1280 } 1281 state = 2 1282 case 2: 1283 if b[i] != 'M' { 1284 return false, false 1285 } 1286 state++ 1287 case 3: 1288 btn = int(b[i]) 1289 state++ 1290 case 4: 1291 x = int(b[i]) - 32 - 1 1292 state++ 1293 case 5: 1294 y = int(b[i]) - 32 - 1 1295 for i >= 0 { 1296 _, _ = buf.ReadByte() 1297 i-- 1298 } 1299 *evs = append(*evs, t.buildMouseEvent(x, y, btn)) 1300 return true, true 1301 } 1302 } 1303 return true, false 1304} 1305 1306func (t *tScreen) parseFunctionKey(buf *bytes.Buffer, evs *[]Event) (bool, bool) { 1307 b := buf.Bytes() 1308 partial := false 1309 for e, k := range t.keycodes { 1310 esc := []byte(e) 1311 if (len(esc) == 1) && (esc[0] == '\x1b') { 1312 continue 1313 } 1314 if bytes.HasPrefix(b, esc) { 1315 // matched 1316 var r rune 1317 if len(esc) == 1 { 1318 r = rune(b[0]) 1319 } 1320 mod := k.mod 1321 if t.escaped { 1322 mod |= ModAlt 1323 t.escaped = false 1324 } 1325 switch k.key { 1326 case keyPasteStart: 1327 *evs = append(*evs, NewEventPaste(true)) 1328 case keyPasteEnd: 1329 *evs = append(*evs, NewEventPaste(false)) 1330 default: 1331 *evs = append(*evs, NewEventKey(k.key, r, mod)) 1332 } 1333 for i := 0; i < len(esc); i++ { 1334 _, _ = buf.ReadByte() 1335 } 1336 return true, true 1337 } 1338 if bytes.HasPrefix(esc, b) { 1339 partial = true 1340 } 1341 } 1342 return partial, false 1343} 1344 1345func (t *tScreen) parseRune(buf *bytes.Buffer, evs *[]Event) (bool, bool) { 1346 b := buf.Bytes() 1347 if b[0] >= ' ' && b[0] <= 0x7F { 1348 // printable ASCII easy to deal with -- no encodings 1349 mod := ModNone 1350 if t.escaped { 1351 mod = ModAlt 1352 t.escaped = false 1353 } 1354 *evs = append(*evs, NewEventKey(KeyRune, rune(b[0]), mod)) 1355 _, _ = buf.ReadByte() 1356 return true, true 1357 } 1358 1359 if b[0] < 0x80 { 1360 // Low numbered values are control keys, not runes. 1361 return false, false 1362 } 1363 1364 utf := make([]byte, 12) 1365 for l := 1; l <= len(b); l++ { 1366 t.decoder.Reset() 1367 nOut, nIn, e := t.decoder.Transform(utf, b[:l], true) 1368 if e == transform.ErrShortSrc { 1369 continue 1370 } 1371 if nOut != 0 { 1372 r, _ := utf8.DecodeRune(utf[:nOut]) 1373 if r != utf8.RuneError { 1374 mod := ModNone 1375 if t.escaped { 1376 mod = ModAlt 1377 t.escaped = false 1378 } 1379 *evs = append(*evs, NewEventKey(KeyRune, r, mod)) 1380 } 1381 for nIn > 0 { 1382 _, _ = buf.ReadByte() 1383 nIn-- 1384 } 1385 return true, true 1386 } 1387 } 1388 // Looks like potential escape 1389 return true, false 1390} 1391 1392func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) { 1393 evs := t.collectEventsFromInput(buf, expire) 1394 1395 for _, ev := range evs { 1396 t.PostEventWait(ev) 1397 } 1398} 1399 1400// Return an array of Events extracted from the supplied buffer. This is done 1401// while holding the screen's lock - the events can then be queued for 1402// application processing with the lock released. 1403func (t *tScreen) collectEventsFromInput(buf *bytes.Buffer, expire bool) []Event { 1404 1405 res := make([]Event, 0, 20) 1406 1407 t.Lock() 1408 defer t.Unlock() 1409 1410 for { 1411 b := buf.Bytes() 1412 if len(b) == 0 { 1413 buf.Reset() 1414 return res 1415 } 1416 1417 partials := 0 1418 1419 if part, comp := t.parseRune(buf, &res); comp { 1420 continue 1421 } else if part { 1422 partials++ 1423 } 1424 1425 if part, comp := t.parseFunctionKey(buf, &res); comp { 1426 continue 1427 } else if part { 1428 partials++ 1429 } 1430 1431 // Only parse mouse records if this term claims to have 1432 // mouse support 1433 1434 if t.ti.Mouse != "" { 1435 if part, comp := t.parseXtermMouse(buf, &res); comp { 1436 continue 1437 } else if part { 1438 partials++ 1439 } 1440 1441 if part, comp := t.parseSgrMouse(buf, &res); comp { 1442 continue 1443 } else if part { 1444 partials++ 1445 } 1446 } 1447 1448 if partials == 0 || expire { 1449 if b[0] == '\x1b' { 1450 if len(b) == 1 { 1451 res = append(res, NewEventKey(KeyEsc, 0, ModNone)) 1452 t.escaped = false 1453 } else { 1454 t.escaped = true 1455 } 1456 _, _ = buf.ReadByte() 1457 continue 1458 } 1459 // Nothing was going to match, or we timed out 1460 // waiting for more data -- just deliver the characters 1461 // to the app & let them sort it out. Possibly we 1462 // should only do this for control characters like ESC. 1463 by, _ := buf.ReadByte() 1464 mod := ModNone 1465 if t.escaped { 1466 t.escaped = false 1467 mod = ModAlt 1468 } 1469 res = append(res, NewEventKey(KeyRune, rune(by), mod)) 1470 continue 1471 } 1472 1473 // well we have some partial data, wait until we get 1474 // some more 1475 break 1476 } 1477 1478 return res 1479} 1480 1481func (t *tScreen) mainLoop(stopQ chan struct{}) { 1482 defer t.wg.Done() 1483 buf := &bytes.Buffer{} 1484 for { 1485 select { 1486 case <-stopQ: 1487 return 1488 case <-t.quit: 1489 return 1490 case <-t.resizeQ: 1491 t.Lock() 1492 t.cx = -1 1493 t.cy = -1 1494 t.resize() 1495 t.cells.Invalidate() 1496 t.draw() 1497 t.Unlock() 1498 continue 1499 case <-t.keytimer.C: 1500 // If the timer fired, and the current time 1501 // is after the expiration of the escape sequence, 1502 // then we assume the escape sequence reached it's 1503 // conclusion, and process the chunk independently. 1504 // This lets us detect conflicts such as a lone ESC. 1505 if buf.Len() > 0 { 1506 if time.Now().After(t.keyexpire) { 1507 t.scanInput(buf, true) 1508 } 1509 } 1510 if buf.Len() > 0 { 1511 if !t.keytimer.Stop() { 1512 select { 1513 case <-t.keytimer.C: 1514 default: 1515 } 1516 } 1517 t.keytimer.Reset(time.Millisecond * 50) 1518 } 1519 case chunk := <-t.keychan: 1520 buf.Write(chunk) 1521 t.keyexpire = time.Now().Add(time.Millisecond * 50) 1522 t.scanInput(buf, false) 1523 if !t.keytimer.Stop() { 1524 select { 1525 case <-t.keytimer.C: 1526 default: 1527 } 1528 } 1529 if buf.Len() > 0 { 1530 t.keytimer.Reset(time.Millisecond * 50) 1531 } 1532 } 1533 } 1534} 1535 1536func (t *tScreen) inputLoop(stopQ chan struct{}) { 1537 1538 defer t.wg.Done() 1539 for { 1540 select { 1541 case <-stopQ: 1542 return 1543 default: 1544 } 1545 chunk := make([]byte, 128) 1546 n, e := t.tty.Read(chunk) 1547 switch e { 1548 case nil: 1549 default: 1550 _ = t.PostEvent(NewEventError(e)) 1551 return 1552 } 1553 if n > 0 { 1554 t.keychan <- chunk[:n] 1555 } 1556 } 1557} 1558 1559func (t *tScreen) Sync() { 1560 t.Lock() 1561 t.cx = -1 1562 t.cy = -1 1563 if !t.fini { 1564 t.resize() 1565 t.clear = true 1566 t.cells.Invalidate() 1567 t.draw() 1568 } 1569 t.Unlock() 1570} 1571 1572func (t *tScreen) CharacterSet() string { 1573 return t.charset 1574} 1575 1576func (t *tScreen) RegisterRuneFallback(orig rune, fallback string) { 1577 t.Lock() 1578 t.fallback[orig] = fallback 1579 t.Unlock() 1580} 1581 1582func (t *tScreen) UnregisterRuneFallback(orig rune) { 1583 t.Lock() 1584 delete(t.fallback, orig) 1585 t.Unlock() 1586} 1587 1588func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool { 1589 1590 if enc := t.encoder; enc != nil { 1591 nb := make([]byte, 6) 1592 ob := make([]byte, 6) 1593 num := utf8.EncodeRune(ob, r) 1594 1595 enc.Reset() 1596 dst, _, err := enc.Transform(nb, ob[:num], true) 1597 if dst != 0 && err == nil && nb[0] != '\x1A' { 1598 return true 1599 } 1600 } 1601 // Terminal fallbacks always permitted, since we assume they are 1602 // basically nearly perfect renditions. 1603 if _, ok := t.acs[r]; ok { 1604 return true 1605 } 1606 if !checkFallbacks { 1607 return false 1608 } 1609 if _, ok := t.fallback[r]; ok { 1610 return true 1611 } 1612 return false 1613} 1614 1615func (t *tScreen) HasMouse() bool { 1616 return len(t.mouse) != 0 1617} 1618 1619func (t *tScreen) HasKey(k Key) bool { 1620 if k == KeyRune { 1621 return true 1622 } 1623 return t.keyexist[k] 1624} 1625 1626func (t *tScreen) Resize(int, int, int, int) {} 1627 1628func (t *tScreen) Suspend() error { 1629 t.disengage() 1630 return nil 1631} 1632 1633func (t *tScreen) Resume() error { 1634 return t.engage() 1635} 1636 1637// engage is used to place the terminal in raw mode and establish screen size, etc. 1638// Thing of this is as tcell "engaging" the clutch, as it's going to be driving the 1639// terminal interface. 1640func (t *tScreen) engage() error { 1641 t.Lock() 1642 defer t.Unlock() 1643 if t.tty == nil { 1644 return ErrNoScreen 1645 } 1646 t.tty.NotifyResize(func() { 1647 select { 1648 case t.resizeQ <- true: 1649 default: 1650 } 1651 }) 1652 if t.running { 1653 return errors.New("already engaged") 1654 } 1655 if err := t.tty.Start(); err != nil { 1656 return err 1657 } 1658 t.running = true 1659 if w, h, err := t.tty.WindowSize(); err == nil && w != 0 && h != 0 { 1660 t.cells.Resize(w, h) 1661 } 1662 stopQ := make(chan struct{}) 1663 t.stopQ = stopQ 1664 t.enableMouse(t.mouseFlags) 1665 t.enablePasting(t.pasteEnabled) 1666 1667 ti := t.ti 1668 t.TPuts(ti.EnterCA) 1669 t.TPuts(ti.EnterKeypad) 1670 t.TPuts(ti.HideCursor) 1671 t.TPuts(ti.EnableAcs) 1672 t.TPuts(ti.Clear) 1673 1674 t.wg.Add(2) 1675 go t.inputLoop(stopQ) 1676 go t.mainLoop(stopQ) 1677 return nil 1678} 1679 1680// disengage is used to release the terminal back to support from the caller. 1681// Think of this as tcell disengaging the clutch, so that another application 1682// can take over the terminal interface. This restores the TTY mode that was 1683// present when the application was first started. 1684func (t *tScreen) disengage() { 1685 1686 t.Lock() 1687 if !t.running { 1688 t.Unlock() 1689 return 1690 } 1691 t.running = false 1692 stopQ := t.stopQ 1693 close(stopQ) 1694 _ = t.tty.Drain() 1695 t.Unlock() 1696 1697 t.tty.NotifyResize(nil) 1698 // wait for everything to shut down 1699 t.wg.Wait() 1700 1701 // shutdown the screen and disable special modes (e.g. mouse and bracketed paste) 1702 ti := t.ti 1703 t.cells.Resize(0, 0) 1704 t.TPuts(ti.ShowCursor) 1705 t.TPuts(ti.ResetFgBg) 1706 t.TPuts(ti.AttrOff) 1707 t.TPuts(ti.Clear) 1708 t.TPuts(ti.ExitCA) 1709 t.TPuts(ti.ExitKeypad) 1710 t.enableMouse(0) 1711 t.enablePasting(false) 1712 1713 _ = t.tty.Stop() 1714} 1715 1716// Beep emits a beep to the terminal. 1717func (t *tScreen) Beep() error { 1718 t.writeString(string(byte(7))) 1719 return nil 1720} 1721 1722// finalize is used to at application shutdown, and restores the terminal 1723// to it's initial state. It should not be called more than once. 1724func (t *tScreen) finalize() { 1725 t.disengage() 1726 _ = t.tty.Close() 1727} 1728