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 }