simulation.go (10670B)
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 package tcell 16 17 import ( 18 "sync" 19 "unicode/utf8" 20 21 "golang.org/x/text/transform" 22 ) 23 24 // NewSimulationScreen returns a SimulationScreen. Note that 25 // SimulationScreen is also a Screen. 26 func NewSimulationScreen(charset string) SimulationScreen { 27 if charset == "" { 28 charset = "UTF-8" 29 } 30 ss := &simscreen{charset: charset} 31 ss.Screen = &baseScreen{screenImpl: ss} 32 return ss 33 } 34 35 // SimulationScreen represents a screen simulation. This is intended to 36 // be a superset of normal Screens, but also adds some important interfaces 37 // for testing. 38 type SimulationScreen interface { 39 Screen 40 41 // InjectKeyBytes injects a stream of bytes corresponding to 42 // the native encoding (see charset). It turns true if the entire 43 // set of bytes were processed and delivered as KeyEvents, false 44 // if any bytes were not fully understood. Any bytes that are not 45 // fully converted are discarded. 46 InjectKeyBytes(buf []byte) bool 47 48 // InjectKey injects a key event. The rune is a UTF-8 rune, post 49 // any translation. 50 InjectKey(key Key, r rune, mod ModMask) 51 52 // InjectMouse injects a mouse event. 53 InjectMouse(x, y int, buttons ButtonMask, mod ModMask) 54 55 // GetContents returns screen contents as an array of 56 // cells, along with the physical width & height. Note that the 57 // physical contents will be used until the next time SetSize() 58 // is called. 59 GetContents() (cells []SimCell, width int, height int) 60 61 // GetCursor returns the cursor details. 62 GetCursor() (x int, y int, visible bool) 63 64 // GetTitle gets the previously set title. 65 GetTitle() string 66 67 // GetClipboardData gets the actual data for the clipboard. 68 GetClipboardData() []byte 69 } 70 71 // SimCell represents a simulated screen cell. The purpose of this 72 // is to track on screen content. 73 type SimCell struct { 74 // Bytes is the actual character bytes. Normally this is 75 // rune data, but it could be be data in another encoding system. 76 Bytes []byte 77 78 // Style is the style used to display the data. 79 Style Style 80 81 // Runes is the list of runes, unadulterated, in UTF-8. 82 Runes []rune 83 } 84 85 type simscreen struct { 86 physw int 87 physh int 88 fini bool 89 style Style 90 evch chan Event 91 quit chan struct{} 92 93 front []SimCell 94 back CellBuffer 95 clear bool 96 cursorx int 97 cursory int 98 cursorvis bool 99 mouse bool 100 paste bool 101 charset string 102 encoder transform.Transformer 103 decoder transform.Transformer 104 fillchar rune 105 fillstyle Style 106 fallback map[rune]string 107 title string 108 clipboard []byte 109 110 Screen 111 sync.Mutex 112 } 113 114 func (s *simscreen) Init() error { 115 s.evch = make(chan Event, 10) 116 s.quit = make(chan struct{}) 117 s.fillchar = 'X' 118 s.fillstyle = StyleDefault 119 s.mouse = false 120 s.physw = 80 121 s.physh = 25 122 s.cursorx = -1 123 s.cursory = -1 124 s.style = StyleDefault 125 126 if enc := GetEncoding(s.charset); enc != nil { 127 s.encoder = enc.NewEncoder() 128 s.decoder = enc.NewDecoder() 129 } else { 130 return ErrNoCharset 131 } 132 133 s.front = make([]SimCell, s.physw*s.physh) 134 s.back.Resize(80, 25) 135 136 // default fallbacks 137 s.fallback = make(map[rune]string) 138 for k, v := range RuneFallbacks { 139 s.fallback[k] = v 140 } 141 return nil 142 } 143 144 func (s *simscreen) Fini() { 145 s.Lock() 146 s.fini = true 147 s.back.Resize(0, 0) 148 s.Unlock() 149 if s.quit != nil { 150 close(s.quit) 151 } 152 s.physw = 0 153 s.physh = 0 154 s.front = nil 155 } 156 157 func (s *simscreen) SetStyle(style Style) { 158 s.Lock() 159 s.style = style 160 s.Unlock() 161 } 162 163 func (s *simscreen) drawCell(x, y int) int { 164 165 mainc, combc, style, width := s.back.GetContent(x, y) 166 if !s.back.Dirty(x, y) { 167 return width 168 } 169 if x >= s.physw || y >= s.physh || x < 0 || y < 0 { 170 return width 171 } 172 simc := &s.front[(y*s.physw)+x] 173 174 if style == StyleDefault { 175 style = s.style 176 } 177 simc.Style = style 178 simc.Runes = append([]rune{mainc}, combc...) 179 180 // now emit runes - taking care to not overrun width with a 181 // wide character, and to ensure that we emit exactly one regular 182 // character followed up by any residual combing characters 183 184 simc.Bytes = nil 185 186 if x > s.physw-width { 187 simc.Runes = []rune{' '} 188 simc.Bytes = []byte{' '} 189 return width 190 } 191 192 lbuf := make([]byte, 12) 193 ubuf := make([]byte, 12) 194 nout := 0 195 196 for _, r := range simc.Runes { 197 198 l := utf8.EncodeRune(ubuf, r) 199 200 nout, _, _ = s.encoder.Transform(lbuf, ubuf[:l], true) 201 202 if nout == 0 || lbuf[0] == '\x1a' { 203 204 // skip combining 205 206 if subst, ok := s.fallback[r]; ok { 207 simc.Bytes = append(simc.Bytes, 208 []byte(subst)...) 209 210 } else if r >= ' ' && r <= '~' { 211 simc.Bytes = append(simc.Bytes, byte(r)) 212 213 } else if simc.Bytes == nil { 214 simc.Bytes = append(simc.Bytes, '?') 215 } 216 } else { 217 simc.Bytes = append(simc.Bytes, lbuf[:nout]...) 218 } 219 } 220 s.back.SetDirty(x, y, false) 221 return width 222 } 223 224 func (s *simscreen) ShowCursor(x, y int) { 225 s.Lock() 226 s.cursorx, s.cursory = x, y 227 s.showCursor() 228 s.Unlock() 229 } 230 231 func (s *simscreen) HideCursor() { 232 s.ShowCursor(-1, -1) 233 } 234 235 func (s *simscreen) showCursor() { 236 237 x, y := s.cursorx, s.cursory 238 if x < 0 || y < 0 || x >= s.physw || y >= s.physh { 239 s.cursorvis = false 240 } else { 241 s.cursorvis = true 242 } 243 } 244 245 func (s *simscreen) hideCursor() { 246 // does not update cursor position 247 s.cursorvis = false 248 } 249 250 func (s *simscreen) SetCursor(CursorStyle, Color) {} 251 252 func (s *simscreen) Show() { 253 s.Lock() 254 s.resize() 255 s.draw() 256 s.Unlock() 257 } 258 259 func (s *simscreen) clearScreen() { 260 // We emulate a hardware clear by filling with a specific pattern 261 for i := range s.front { 262 s.front[i].Style = s.fillstyle 263 s.front[i].Runes = []rune{s.fillchar} 264 s.front[i].Bytes = []byte{byte(s.fillchar)} 265 } 266 s.clear = false 267 } 268 269 func (s *simscreen) draw() { 270 s.hideCursor() 271 if s.clear { 272 s.clearScreen() 273 } 274 275 w, h := s.back.Size() 276 for y := 0; y < h; y++ { 277 for x := 0; x < w; x++ { 278 width := s.drawCell(x, y) 279 x += width - 1 280 } 281 } 282 s.showCursor() 283 } 284 285 func (s *simscreen) EnableMouse(...MouseFlags) { 286 s.mouse = true 287 } 288 289 func (s *simscreen) DisableMouse() { 290 s.mouse = false 291 } 292 293 func (s *simscreen) EnablePaste() { 294 s.paste = true 295 } 296 297 func (s *simscreen) DisablePaste() { 298 s.paste = false 299 } 300 301 func (s *simscreen) EnableFocus() { 302 } 303 304 func (s *simscreen) DisableFocus() { 305 } 306 307 func (s *simscreen) Size() (int, int) { 308 s.Lock() 309 w, h := s.back.Size() 310 s.Unlock() 311 return w, h 312 } 313 314 func (s *simscreen) resize() { 315 w, h := s.physw, s.physh 316 ow, oh := s.back.Size() 317 if w != ow || h != oh { 318 s.back.Resize(w, h) 319 ev := NewEventResize(w, h) 320 s.postEvent(ev) 321 } 322 } 323 324 func (s *simscreen) Colors() int { 325 return 256 326 } 327 328 func (s *simscreen) postEvent(ev Event) { 329 select { 330 case s.evch <- ev: 331 case <-s.quit: 332 } 333 } 334 335 func (s *simscreen) InjectMouse(x, y int, buttons ButtonMask, mod ModMask) { 336 ev := NewEventMouse(x, y, buttons, mod) 337 s.postEvent(ev) 338 } 339 340 func (s *simscreen) InjectKey(key Key, r rune, mod ModMask) { 341 ev := NewEventKey(key, r, mod) 342 s.postEvent(ev) 343 } 344 345 func (s *simscreen) InjectKeyBytes(b []byte) bool { 346 failed := false 347 348 outer: 349 for len(b) > 0 { 350 if b[0] >= ' ' && b[0] <= 0x7F { 351 // printable ASCII easy to deal with -- no encodings 352 ev := NewEventKey(KeyRune, rune(b[0]), ModNone) 353 s.postEvent(ev) 354 b = b[1:] 355 continue 356 } 357 358 if b[0] < 0x80 { 359 mod := ModNone 360 // No encodings start with low numbered values 361 if Key(b[0]) >= KeyCtrlA && Key(b[0]) <= KeyCtrlZ { 362 mod = ModCtrl 363 } 364 ev := NewEventKey(Key(b[0]), 0, mod) 365 s.postEvent(ev) 366 b = b[1:] 367 continue 368 } 369 370 utfb := make([]byte, len(b)*4) // worst case 371 for l := 1; l < len(b); l++ { 372 s.decoder.Reset() 373 nout, nin, _ := s.decoder.Transform(utfb, b[:l], true) 374 375 if nout != 0 { 376 r, _ := utf8.DecodeRune(utfb[:nout]) 377 if r != utf8.RuneError { 378 ev := NewEventKey(KeyRune, r, ModNone) 379 s.postEvent(ev) 380 } 381 b = b[nin:] 382 continue outer 383 } 384 } 385 failed = true 386 b = b[1:] 387 continue 388 } 389 390 return !failed 391 } 392 393 func (s *simscreen) Sync() { 394 s.Lock() 395 s.clear = true 396 s.resize() 397 s.back.Invalidate() 398 s.draw() 399 s.Unlock() 400 } 401 402 func (s *simscreen) CharacterSet() string { 403 return s.charset 404 } 405 406 func (s *simscreen) SetSize(w, h int) { 407 s.Lock() 408 newc := make([]SimCell, w*h) 409 for row := 0; row < h && row < s.physh; row++ { 410 for col := 0; col < w && col < s.physw; col++ { 411 newc[(row*w)+col] = s.front[(row*s.physw)+col] 412 } 413 } 414 s.cursorx, s.cursory = -1, -1 415 s.physw, s.physh = w, h 416 s.front = newc 417 s.back.Resize(w, h) 418 s.Unlock() 419 } 420 421 func (s *simscreen) GetContents() ([]SimCell, int, int) { 422 s.Lock() 423 cells, w, h := s.front, s.physw, s.physh 424 s.Unlock() 425 return cells, w, h 426 } 427 428 func (s *simscreen) GetCursor() (int, int, bool) { 429 s.Lock() 430 x, y, vis := s.cursorx, s.cursory, s.cursorvis 431 s.Unlock() 432 return x, y, vis 433 } 434 435 func (s *simscreen) RegisterRuneFallback(r rune, subst string) { 436 s.Lock() 437 s.fallback[r] = subst 438 s.Unlock() 439 } 440 441 func (s *simscreen) UnregisterRuneFallback(r rune) { 442 s.Lock() 443 delete(s.fallback, r) 444 s.Unlock() 445 } 446 447 func (s *simscreen) CanDisplay(r rune, checkFallbacks bool) bool { 448 449 if enc := s.encoder; enc != nil { 450 nb := make([]byte, 6) 451 ob := make([]byte, 6) 452 num := utf8.EncodeRune(ob, r) 453 454 enc.Reset() 455 dst, _, err := enc.Transform(nb, ob[:num], true) 456 if dst != 0 && err == nil && nb[0] != '\x1A' { 457 return true 458 } 459 } 460 if !checkFallbacks { 461 return false 462 } 463 if _, ok := s.fallback[r]; ok { 464 return true 465 } 466 return false 467 } 468 469 func (s *simscreen) HasMouse() bool { 470 return false 471 } 472 473 func (s *simscreen) Resize(int, int, int, int) {} 474 475 func (s *simscreen) HasKey(Key) bool { 476 return true 477 } 478 479 func (s *simscreen) Beep() error { 480 return nil 481 } 482 483 func (s *simscreen) Suspend() error { 484 return nil 485 } 486 487 func (s *simscreen) Resume() error { 488 return nil 489 } 490 491 func (s *simscreen) Tty() (Tty, bool) { 492 return nil, false 493 } 494 495 func (s *simscreen) GetCells() *CellBuffer { 496 return &s.back 497 } 498 499 func (s *simscreen) EventQ() chan Event { 500 return s.evch 501 } 502 503 func (s *simscreen) StopQ() <-chan struct{} { 504 return s.quit 505 } 506 507 func (s *simscreen) SetTitle(title string) { 508 s.title = title 509 } 510 511 func (s *simscreen) GetTitle() string { 512 return s.title 513 } 514 515 func (s *simscreen) SetClipboard(data []byte) { 516 s.clipboard = data 517 } 518 519 func (s *simscreen) GetClipboard() { 520 if s.clipboard != nil { 521 ev := NewEventClipboard(s.clipboard) 522 s.postEvent(ev) 523 } 524 } 525 526 func (s *simscreen) GetClipboardData() []byte { 527 return s.clipboard 528 }