wscreen.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. // Copyright 2023 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. //go:build js && wasm
  15. // +build js,wasm
  16. package tcell
  17. import (
  18. "errors"
  19. "strings"
  20. "sync"
  21. "syscall/js"
  22. "unicode/utf8"
  23. )
  24. func NewTerminfoScreen() (Screen, error) {
  25. t := &wScreen{}
  26. t.fallback = make(map[rune]string)
  27. return t, nil
  28. }
  29. type wScreen struct {
  30. w, h int
  31. style Style
  32. cells CellBuffer
  33. running bool
  34. clear bool
  35. flagsPresent bool
  36. pasteEnabled bool
  37. mouseFlags MouseFlags
  38. cursorStyle CursorStyle
  39. quit chan struct{}
  40. evch chan Event
  41. fallback map[rune]string
  42. sync.Mutex
  43. }
  44. func (t *wScreen) Init() error {
  45. t.w, t.h = 80, 24 // default for html as of now
  46. t.evch = make(chan Event, 10)
  47. t.quit = make(chan struct{})
  48. t.Lock()
  49. t.running = true
  50. t.style = StyleDefault
  51. t.cells.Resize(t.w, t.h)
  52. t.Unlock()
  53. js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent))
  54. return nil
  55. }
  56. func (t *wScreen) Fini() {
  57. close(t.quit)
  58. }
  59. func (t *wScreen) SetStyle(style Style) {
  60. t.Lock()
  61. t.style = style
  62. t.Unlock()
  63. }
  64. func (t *wScreen) Clear() {
  65. t.Fill(' ', t.style)
  66. }
  67. func (t *wScreen) Fill(r rune, style Style) {
  68. t.Lock()
  69. t.cells.Fill(r, style)
  70. t.Unlock()
  71. }
  72. func (t *wScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
  73. t.Lock()
  74. t.cells.SetContent(x, y, mainc, combc, style)
  75. t.Unlock()
  76. }
  77. func (t *wScreen) GetContent(x, y int) (rune, []rune, Style, int) {
  78. t.Lock()
  79. mainc, combc, style, width := t.cells.GetContent(x, y)
  80. t.Unlock()
  81. return mainc, combc, style, width
  82. }
  83. func (t *wScreen) SetCell(x, y int, style Style, ch ...rune) {
  84. if len(ch) > 0 {
  85. t.SetContent(x, y, ch[0], ch[1:], style)
  86. } else {
  87. t.SetContent(x, y, ' ', nil, style)
  88. }
  89. }
  90. // paletteColor gives a more natural palette color actually matching
  91. // typical XTerm. We might in the future want to permit styling these
  92. // via CSS.
  93. var palette = map[Color]int32{
  94. ColorBlack: 0x000000,
  95. ColorMaroon: 0xcd0000,
  96. ColorGreen: 0x00cd00,
  97. ColorOlive: 0xcdcd00,
  98. ColorNavy: 0x0000ee,
  99. ColorPurple: 0xcd00cd,
  100. ColorTeal: 0x00cdcd,
  101. ColorSilver: 0xe5e5e5,
  102. ColorGray: 0x7f7f7f,
  103. ColorRed: 0xff0000,
  104. ColorLime: 0x00ff00,
  105. ColorYellow: 0xffff00,
  106. ColorBlue: 0x5c5cff,
  107. ColorFuchsia: 0xff00ff,
  108. ColorAqua: 0x00ffff,
  109. ColorWhite: 0xffffff,
  110. }
  111. func paletteColor(c Color) int32 {
  112. if (c.IsRGB()) {
  113. return int32(c & 0xffffff);
  114. }
  115. if (c >= ColorBlack && c <= ColorWhite) {
  116. return palette[c]
  117. }
  118. return c.Hex()
  119. }
  120. func (t *wScreen) drawCell(x, y int) int {
  121. mainc, combc, style, width := t.cells.GetContent(x, y)
  122. if !t.cells.Dirty(x, y) {
  123. return width
  124. }
  125. if style == StyleDefault {
  126. style = t.style
  127. }
  128. fg, bg := paletteColor(style.fg), paletteColor(style.bg)
  129. if (fg == -1) {
  130. fg = 0xe5e5e5;
  131. }
  132. if (bg == -1) {
  133. bg = 0x000000;
  134. }
  135. var combcarr []interface{} = make([]interface{}, len(combc))
  136. for i, c := range combc {
  137. combcarr[i] = c
  138. }
  139. t.cells.SetDirty(x, y, false)
  140. js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs))
  141. return width
  142. }
  143. func (t *wScreen) ShowCursor(x, y int) {
  144. t.Lock()
  145. js.Global().Call("showCursor", x, y)
  146. t.Unlock()
  147. }
  148. func (t *wScreen) SetCursorStyle(cs CursorStyle) {
  149. t.Lock()
  150. js.Global().Call("setCursorStyle", curStyleClasses[cs])
  151. t.Unlock()
  152. }
  153. func (t *wScreen) HideCursor() {
  154. t.ShowCursor(-1, -1)
  155. }
  156. func (t *wScreen) Show() {
  157. t.Lock()
  158. t.resize()
  159. t.draw()
  160. t.Unlock()
  161. }
  162. func (t *wScreen) clearScreen() {
  163. js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
  164. t.clear = false
  165. }
  166. func (t *wScreen) draw() {
  167. if t.clear {
  168. t.clearScreen()
  169. }
  170. for y := 0; y < t.h; y++ {
  171. for x := 0; x < t.w; x++ {
  172. width := t.drawCell(x, y)
  173. x += width - 1
  174. }
  175. }
  176. js.Global().Call("show")
  177. }
  178. func (t *wScreen) EnableMouse(flags ...MouseFlags) {
  179. var f MouseFlags
  180. flagsPresent := false
  181. for _, flag := range flags {
  182. f |= flag
  183. flagsPresent = true
  184. }
  185. if !flagsPresent {
  186. f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
  187. }
  188. t.Lock()
  189. t.mouseFlags = f
  190. t.enableMouse(f)
  191. t.Unlock()
  192. }
  193. func (t *wScreen) enableMouse(f MouseFlags) {
  194. if f&MouseButtonEvents != 0 {
  195. js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent))
  196. } else {
  197. js.Global().Set("onMouseClick", js.FuncOf(t.unset))
  198. }
  199. if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
  200. js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
  201. } else {
  202. js.Global().Set("onMouseMove", js.FuncOf(t.unset))
  203. }
  204. }
  205. func (t *wScreen) DisableMouse() {
  206. t.Lock()
  207. t.mouseFlags = 0
  208. t.enableMouse(0)
  209. t.Unlock()
  210. }
  211. func (t *wScreen) EnablePaste() {
  212. t.Lock()
  213. t.pasteEnabled = true
  214. t.enablePasting(true)
  215. t.Unlock()
  216. }
  217. func (t *wScreen) DisablePaste() {
  218. t.Lock()
  219. t.pasteEnabled = false
  220. t.enablePasting(false)
  221. t.Unlock()
  222. }
  223. func (t *wScreen) enablePasting(on bool) {
  224. if on {
  225. js.Global().Set("onPaste", js.FuncOf(t.onPaste))
  226. } else {
  227. js.Global().Set("onPaste", js.FuncOf(t.unset))
  228. }
  229. }
  230. func (t *wScreen) Size() (int, int) {
  231. t.Lock()
  232. w, h := t.w, t.h
  233. t.Unlock()
  234. return w, h
  235. }
  236. // resize does nothing, as asking the web window to resize
  237. // without a specified width or height will cause no change.
  238. func (t *wScreen) resize() {}
  239. func (t *wScreen) Colors() int {
  240. return 16777216 // 256 ^ 3
  241. }
  242. func (t *wScreen) ChannelEvents(ch chan<- Event, quit <-chan struct{}) {
  243. defer close(ch)
  244. for {
  245. select {
  246. case <-quit:
  247. return
  248. case <-t.quit:
  249. return
  250. case ev := <-t.evch:
  251. select {
  252. case <-quit:
  253. return
  254. case <-t.quit:
  255. return
  256. case ch <- ev:
  257. }
  258. }
  259. }
  260. }
  261. func (t *wScreen) PollEvent() Event {
  262. select {
  263. case <-t.quit:
  264. return nil
  265. case ev := <-t.evch:
  266. return ev
  267. }
  268. }
  269. func (t *wScreen) HasPendingEvent() bool {
  270. return len(t.evch) > 0
  271. }
  272. func (t *wScreen) PostEventWait(ev Event) {
  273. t.evch <- ev
  274. }
  275. func (t *wScreen) PostEvent(ev Event) error {
  276. select {
  277. case t.evch <- ev:
  278. return nil
  279. default:
  280. return ErrEventQFull
  281. }
  282. }
  283. func (t *wScreen) clip(x, y int) (int, int) {
  284. w, h := t.cells.Size()
  285. if x < 0 {
  286. x = 0
  287. }
  288. if y < 0 {
  289. y = 0
  290. }
  291. if x > w-1 {
  292. x = w - 1
  293. }
  294. if y > h-1 {
  295. y = h - 1
  296. }
  297. return x, y
  298. }
  299. func (t *wScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
  300. mod := ModNone
  301. button := ButtonNone
  302. switch args[2].Int() {
  303. case 0:
  304. if t.mouseFlags&MouseMotionEvents == 0 {
  305. // don't want this event! is a mouse motion event, but user has asked not.
  306. return nil
  307. }
  308. button = ButtonNone
  309. case 1:
  310. button = Button1
  311. case 2:
  312. button = Button3 // Note we prefer to treat right as button 2
  313. case 3:
  314. button = Button2 // And the middle button as button 3
  315. }
  316. if args[3].Bool() { // mod shift
  317. mod |= ModShift
  318. }
  319. if args[4].Bool() { // mod alt
  320. mod |= ModAlt
  321. }
  322. if args[5].Bool() { // mod ctrl
  323. mod |= ModCtrl
  324. }
  325. t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
  326. return nil
  327. }
  328. func (t *wScreen) onKeyEvent(this js.Value, args []js.Value) interface{} {
  329. key := args[0].String()
  330. // don't accept any modifier keys as their own
  331. if key == "Control" || key == "Alt" || key == "Meta" || key == "Shift" {
  332. return nil
  333. }
  334. mod := ModNone
  335. if args[1].Bool() { // mod shift
  336. mod |= ModShift
  337. }
  338. if args[2].Bool() { // mod alt
  339. mod |= ModAlt
  340. }
  341. if args[3].Bool() { // mod ctrl
  342. mod |= ModCtrl
  343. }
  344. if args[4].Bool() { // mod meta
  345. mod |= ModMeta
  346. }
  347. // check for special case of Ctrl + key
  348. if mod == ModCtrl {
  349. if k, ok := WebKeyNames["Ctrl-"+strings.ToLower(key)]; ok {
  350. t.PostEventWait(NewEventKey(k, 0, mod))
  351. return nil
  352. }
  353. }
  354. // next try function keys
  355. if k, ok := WebKeyNames[key]; ok {
  356. t.PostEventWait(NewEventKey(k, 0, mod))
  357. return nil
  358. }
  359. // finally try normal, printable chars
  360. r, _ := utf8.DecodeRuneInString(key)
  361. t.PostEventWait(NewEventKey(KeyRune, r, mod))
  362. return nil
  363. }
  364. func (t *wScreen) onPaste(this js.Value, args []js.Value) interface{} {
  365. t.PostEventWait(NewEventPaste(args[0].Bool()))
  366. return nil
  367. }
  368. // unset is a dummy function for js when we want nothing to
  369. // happen when javascript calls a function (for example, when
  370. // mouse input is disabled, when onMouseEvent() is called from
  371. // js, it redirects here and does nothing).
  372. func (t *wScreen) unset(this js.Value, args []js.Value) interface{} {
  373. return nil
  374. }
  375. func (t *wScreen) Sync() {
  376. t.Lock()
  377. t.resize()
  378. t.clear = true
  379. t.cells.Invalidate()
  380. t.draw()
  381. t.Unlock()
  382. }
  383. func (t *wScreen) CharacterSet() string {
  384. return "UTF-8"
  385. }
  386. func (t *wScreen) RegisterRuneFallback(orig rune, fallback string) {
  387. t.Lock()
  388. t.fallback[orig] = fallback
  389. t.Unlock()
  390. }
  391. func (t *wScreen) UnregisterRuneFallback(orig rune) {
  392. t.Lock()
  393. delete(t.fallback, orig)
  394. t.Unlock()
  395. }
  396. func (t *wScreen) CanDisplay(r rune, checkFallbacks bool) bool {
  397. if utf8.ValidRune(r) {
  398. return true
  399. }
  400. if !checkFallbacks {
  401. return false
  402. }
  403. if _, ok := t.fallback[r]; ok {
  404. return true
  405. }
  406. return false
  407. }
  408. func (t *wScreen) HasMouse() bool {
  409. return true
  410. }
  411. func (t *wScreen) HasKey(k Key) bool {
  412. return true
  413. }
  414. func (t *wScreen) SetSize(w, h int) {
  415. if w == t.w && h == t.h {
  416. return
  417. }
  418. t.cells.Invalidate()
  419. t.cells.Resize(w, h)
  420. js.Global().Call("resize", w, h)
  421. t.w, t.h = w, h
  422. t.PostEvent(NewEventResize(w, h))
  423. }
  424. func (t *wScreen) Resize(int, int, int, int) {}
  425. // Suspend simply pauses all input and output, and clears the screen.
  426. // There isn't a "default terminal" to go back to.
  427. func (t *wScreen) Suspend() error {
  428. t.Lock()
  429. if !t.running {
  430. t.Unlock()
  431. return nil
  432. }
  433. t.running = false
  434. t.clearScreen()
  435. t.enableMouse(0)
  436. t.enablePasting(false)
  437. js.Global().Set("onKeyEvent", js.FuncOf(t.unset)) // stop keypresses
  438. return nil
  439. }
  440. func (t *wScreen) Resume() error {
  441. t.Lock()
  442. if t.running {
  443. return errors.New("already engaged")
  444. }
  445. t.running = true
  446. t.enableMouse(t.mouseFlags)
  447. t.enablePasting(t.pasteEnabled)
  448. js.Global().Set("onKeyEvent", js.FuncOf(t.onKeyEvent))
  449. t.Unlock()
  450. return nil
  451. }
  452. func (t *wScreen) Beep() error {
  453. js.Global().Call("beep")
  454. return nil
  455. }
  456. // WebKeyNames maps string names reported from HTML
  457. // (KeyboardEvent.key) to tcell accepted keys.
  458. var WebKeyNames = map[string]Key{
  459. "Enter": KeyEnter,
  460. "Backspace": KeyBackspace,
  461. "Tab": KeyTab,
  462. "Backtab": KeyBacktab,
  463. "Escape": KeyEsc,
  464. "Backspace2": KeyBackspace2,
  465. "Delete": KeyDelete,
  466. "Insert": KeyInsert,
  467. "ArrowUp": KeyUp,
  468. "ArrowDown": KeyDown,
  469. "ArrowLeft": KeyLeft,
  470. "ArrowRight": KeyRight,
  471. "Home": KeyHome,
  472. "End": KeyEnd,
  473. "UpLeft": KeyUpLeft, // not supported by HTML
  474. "UpRight": KeyUpRight, // not supported by HTML
  475. "DownLeft": KeyDownLeft, // not supported by HTML
  476. "DownRight": KeyDownRight, // not supported by HTML
  477. "Center": KeyCenter,
  478. "PgDn": KeyPgDn,
  479. "PgUp": KeyPgUp,
  480. "Clear": KeyClear,
  481. "Exit": KeyExit,
  482. "Cancel": KeyCancel,
  483. "Pause": KeyPause,
  484. "Print": KeyPrint,
  485. "F1": KeyF1,
  486. "F2": KeyF2,
  487. "F3": KeyF3,
  488. "F4": KeyF4,
  489. "F5": KeyF5,
  490. "F6": KeyF6,
  491. "F7": KeyF7,
  492. "F8": KeyF8,
  493. "F9": KeyF9,
  494. "F10": KeyF10,
  495. "F11": KeyF11,
  496. "F12": KeyF12,
  497. "F13": KeyF13,
  498. "F14": KeyF14,
  499. "F15": KeyF15,
  500. "F16": KeyF16,
  501. "F17": KeyF17,
  502. "F18": KeyF18,
  503. "F19": KeyF19,
  504. "F20": KeyF20,
  505. "F21": KeyF21,
  506. "F22": KeyF22,
  507. "F23": KeyF23,
  508. "F24": KeyF24,
  509. "F25": KeyF25,
  510. "F26": KeyF26,
  511. "F27": KeyF27,
  512. "F28": KeyF28,
  513. "F29": KeyF29,
  514. "F30": KeyF30,
  515. "F31": KeyF31,
  516. "F32": KeyF32,
  517. "F33": KeyF33,
  518. "F34": KeyF34,
  519. "F35": KeyF35,
  520. "F36": KeyF36,
  521. "F37": KeyF37,
  522. "F38": KeyF38,
  523. "F39": KeyF39,
  524. "F40": KeyF40,
  525. "F41": KeyF41,
  526. "F42": KeyF42,
  527. "F43": KeyF43,
  528. "F44": KeyF44,
  529. "F45": KeyF45,
  530. "F46": KeyF46,
  531. "F47": KeyF47,
  532. "F48": KeyF48,
  533. "F49": KeyF49,
  534. "F50": KeyF50,
  535. "F51": KeyF51,
  536. "F52": KeyF52,
  537. "F53": KeyF53,
  538. "F54": KeyF54,
  539. "F55": KeyF55,
  540. "F56": KeyF56,
  541. "F57": KeyF57,
  542. "F58": KeyF58,
  543. "F59": KeyF59,
  544. "F60": KeyF60,
  545. "F61": KeyF61,
  546. "F62": KeyF62,
  547. "F63": KeyF63,
  548. "F64": KeyF64,
  549. "Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check
  550. "Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check
  551. "Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check
  552. "Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check
  553. "Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check
  554. "Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check
  555. "Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check
  556. "Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check
  557. "Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check
  558. "Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check
  559. "Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check
  560. "Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check
  561. "Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check
  562. "Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check
  563. "Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check
  564. "Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check
  565. "Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check
  566. "Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check
  567. "Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check
  568. "Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check
  569. "Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check
  570. "Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check
  571. "Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check
  572. "Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check
  573. "Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check
  574. "Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check
  575. "Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check
  576. "Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check
  577. }
  578. var curStyleClasses = map[CursorStyle]string{
  579. CursorStyleDefault: "cursor-blinking-block",
  580. CursorStyleBlinkingBlock: "cursor-blinking-block",
  581. CursorStyleSteadyBlock: "cursor-steady-block",
  582. CursorStyleBlinkingUnderline: "cursor-blinking-underline",
  583. CursorStyleSteadyUnderline: "cursor-steady-underline",
  584. CursorStyleBlinkingBar: "cursor-blinking-bar",
  585. CursorStyleSteadyBar: "cursor-steady-bar",
  586. }