buninu 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/apps/jsgotty/LICENSES/MIT-jsmdcui.txt +22 -0
  3. package/apps/jsgotty/NOTICE +5 -0
  4. package/apps/jsgotty/README.en.md +21 -0
  5. package/apps/jsgotty/README.md +21 -0
  6. package/apps/jsgotty/client-modes.mjs +117 -0
  7. package/apps/jsgotty/client.mjs +100 -10
  8. package/apps/jsgotty/clipboard-powershell.js +18 -0
  9. package/apps/jsgotty/clipboard.js +148 -0
  10. package/apps/jsgotty/gotty.js +13 -0
  11. package/apps/jsgotty/package-lock.json +2 -2
  12. package/apps/jsgotty/package.json +1 -1
  13. package/apps/jsgotty/static/css/index.css +14 -1
  14. package/apps/jsgotty/static/js/gotty.js +107 -8
  15. package/apps/jsgotty/static/js/kitty.js +115 -7
  16. package/apps/jsgotty/terminal-modes.js +332 -0
  17. package/apps/jsgotty/tests/client-exit.test.mjs +45 -0
  18. package/apps/jsgotty/tests/client-modes.test.mjs +105 -0
  19. package/apps/jsgotty/tests/clipboard-browser.test.js +81 -0
  20. package/apps/jsgotty/tests/clipboard.test.js +75 -0
  21. package/apps/jsgotty/tests/manual/zoom-collector.cjs +55 -0
  22. package/apps/jsgotty/tests/manual/zoom-debug.js +39 -0
  23. package/apps/jsgotty/tests/manual-touch-diagnostics.js +33 -0
  24. package/apps/jsgotty/tests/manual-touch-diagnostics.mjs +22 -0
  25. package/apps/jsgotty/tests/nested-cli-pty.test.mjs +116 -0
  26. package/apps/jsgotty/tests/reconnect-modes-browser.test.js +144 -0
  27. package/apps/jsgotty/tests/reconnect-modes.md +44 -0
  28. package/apps/jsgotty/tests/reconnect-modes.test.js +166 -0
  29. package/apps/jsgotty/tests/touch-drag-browser.test.js +106 -0
  30. package/apps/jsgotty/tests/wheel-browser.test.js +60 -0
  31. package/apps/jsmdcui/CHANGELOG.md +44 -0
  32. package/apps/jsmdcui/package.json +1 -1
  33. package/apps/jsmdcui/src/highlight/go-regexp.js +83 -0
  34. package/apps/jsmdcui/src/highlight/highlighter.js +49 -62
  35. package/apps/jsmdcui/src/highlight/parser.js +8 -5
  36. package/apps/jsmdcui/tests/go-regexp.test.js +98 -0
  37. package/apps/jsmdcui/tests/heading-ids.test.js +21 -16
  38. package/apps/jsmdcui/tests/highlight-regions.test.js +73 -0
  39. package/apps/jsmdcui/tests/id-collision.test.js +32 -9
  40. package/apps/jsmdcui/tests/markdown-behavior.mjs +9 -0
  41. package/apps/jsmdcui/tests/syntax-parity/NORMAL-HTML.md +46 -0
  42. package/apps/jsmdcui/tests/syntax-parity/README.md +85 -0
  43. package/apps/jsmdcui/tests/syntax-parity/REPORT.md +328 -0
  44. package/apps/jsmdcui/tests/syntax-parity/compare.mjs +75 -0
  45. package/apps/jsmdcui/tests/syntax-parity/go.mod +5 -0
  46. package/apps/jsmdcui/tests/syntax-parity/normal-html-report.json +619 -0
  47. package/apps/jsmdcui/tests/syntax-parity/normal-html.html +9 -0
  48. package/apps/jsmdcui/tests/syntax-parity/oracle.go +49 -0
  49. package/apps/jsmdcui/tests/syntax-parity/report.json +2174 -0
  50. package/apps/jsmdcui/tests/syntax-parity/samples/PowerShell.txt +39 -0
  51. package/apps/jsmdcui/tests/syntax-parity/samples/ada.txt +25 -0
  52. package/apps/jsmdcui/tests/syntax-parity/samples/apacheconf.txt +49 -0
  53. package/apps/jsmdcui/tests/syntax-parity/samples/arduino.txt +38 -0
  54. package/apps/jsmdcui/tests/syntax-parity/samples/asciidoc.txt +33 -0
  55. package/apps/jsmdcui/tests/syntax-parity/samples/asm.txt +77 -0
  56. package/apps/jsmdcui/tests/syntax-parity/samples/ats.txt +48 -0
  57. package/apps/jsmdcui/tests/syntax-parity/samples/awk.txt +28 -0
  58. package/apps/jsmdcui/tests/syntax-parity/samples/b.txt +27 -0
  59. package/apps/jsmdcui/tests/syntax-parity/samples/bat.txt +39 -0
  60. package/apps/jsmdcui/tests/syntax-parity/samples/c.txt +34 -0
  61. package/apps/jsmdcui/tests/syntax-parity/samples/caddyfile.txt +14 -0
  62. package/apps/jsmdcui/tests/syntax-parity/samples/cake.txt +7 -0
  63. package/apps/jsmdcui/tests/syntax-parity/samples/clojure.txt +18 -0
  64. package/apps/jsmdcui/tests/syntax-parity/samples/cmake.txt +20 -0
  65. package/apps/jsmdcui/tests/syntax-parity/samples/coffeescript.txt +32 -0
  66. package/apps/jsmdcui/tests/syntax-parity/samples/colortest.txt +19 -0
  67. package/apps/jsmdcui/tests/syntax-parity/samples/conky.txt +12 -0
  68. package/apps/jsmdcui/tests/syntax-parity/samples/cpp.txt +46 -0
  69. package/apps/jsmdcui/tests/syntax-parity/samples/crontab.txt +16 -0
  70. package/apps/jsmdcui/tests/syntax-parity/samples/crystal.txt +28 -0
  71. package/apps/jsmdcui/tests/syntax-parity/samples/csharp.txt +28 -0
  72. package/apps/jsmdcui/tests/syntax-parity/samples/css.txt +19 -0
  73. package/apps/jsmdcui/tests/syntax-parity/samples/csx.txt +7 -0
  74. package/apps/jsmdcui/tests/syntax-parity/samples/cuda.txt +37 -0
  75. package/apps/jsmdcui/tests/syntax-parity/samples/cython.txt +22 -0
  76. package/apps/jsmdcui/tests/syntax-parity/samples/d.txt +52 -0
  77. package/apps/jsmdcui/tests/syntax-parity/samples/dart.txt +27 -0
  78. package/apps/jsmdcui/tests/syntax-parity/samples/default.txt +8 -0
  79. package/apps/jsmdcui/tests/syntax-parity/samples/dockerfile.txt +10 -0
  80. package/apps/jsmdcui/tests/syntax-parity/samples/dot.txt +15 -0
  81. package/apps/jsmdcui/tests/syntax-parity/samples/elixir.txt +23 -0
  82. package/apps/jsmdcui/tests/syntax-parity/samples/elm.txt +20 -0
  83. package/apps/jsmdcui/tests/syntax-parity/samples/erb.txt +32 -0
  84. package/apps/jsmdcui/tests/syntax-parity/samples/erlang.txt +23 -0
  85. package/apps/jsmdcui/tests/syntax-parity/samples/fish.txt +20 -0
  86. package/apps/jsmdcui/tests/syntax-parity/samples/forth.txt +17 -0
  87. package/apps/jsmdcui/tests/syntax-parity/samples/fortran.txt +40 -0
  88. package/apps/jsmdcui/tests/syntax-parity/samples/freebsd-kernel.txt +10 -0
  89. package/apps/jsmdcui/tests/syntax-parity/samples/fsharp.txt +27 -0
  90. package/apps/jsmdcui/tests/syntax-parity/samples/gdscript.txt +26 -0
  91. package/apps/jsmdcui/tests/syntax-parity/samples/gemini.txt +11 -0
  92. package/apps/jsmdcui/tests/syntax-parity/samples/gentoo-ebuild.txt +28 -0
  93. package/apps/jsmdcui/tests/syntax-parity/samples/gentoo-etc-portage.txt +14 -0
  94. package/apps/jsmdcui/tests/syntax-parity/samples/git-commit.txt +19 -0
  95. package/apps/jsmdcui/tests/syntax-parity/samples/git-config.txt +11 -0
  96. package/apps/jsmdcui/tests/syntax-parity/samples/git-rebase-todo.txt +10 -0
  97. package/apps/jsmdcui/tests/syntax-parity/samples/gleam.txt +44 -0
  98. package/apps/jsmdcui/tests/syntax-parity/samples/glsl.txt +18 -0
  99. package/apps/jsmdcui/tests/syntax-parity/samples/gnuplot.txt +11 -0
  100. package/apps/jsmdcui/tests/syntax-parity/samples/go.txt +33 -0
  101. package/apps/jsmdcui/tests/syntax-parity/samples/godoc.txt +9 -0
  102. package/apps/jsmdcui/tests/syntax-parity/samples/golo.txt +50 -0
  103. package/apps/jsmdcui/tests/syntax-parity/samples/gomod.txt +16 -0
  104. package/apps/jsmdcui/tests/syntax-parity/samples/graphql.txt +20 -0
  105. package/apps/jsmdcui/tests/syntax-parity/samples/groff.txt +18 -0
  106. package/apps/jsmdcui/tests/syntax-parity/samples/groovy.txt +42 -0
  107. package/apps/jsmdcui/tests/syntax-parity/samples/haml.txt +16 -0
  108. package/apps/jsmdcui/tests/syntax-parity/samples/hare.txt +31 -0
  109. package/apps/jsmdcui/tests/syntax-parity/samples/haskell.txt +26 -0
  110. package/apps/jsmdcui/tests/syntax-parity/samples/hc.txt +28 -0
  111. package/apps/jsmdcui/tests/syntax-parity/samples/html.txt +26 -0
  112. package/apps/jsmdcui/tests/syntax-parity/samples/html4.txt +20 -0
  113. package/apps/jsmdcui/tests/syntax-parity/samples/html5.txt +20 -0
  114. package/apps/jsmdcui/tests/syntax-parity/samples/ini.txt +16 -0
  115. package/apps/jsmdcui/tests/syntax-parity/samples/inputrc.txt +14 -0
  116. package/apps/jsmdcui/tests/syntax-parity/samples/java.txt +18 -0
  117. package/apps/jsmdcui/tests/syntax-parity/samples/javascript.txt +41 -0
  118. package/apps/jsmdcui/tests/syntax-parity/samples/jinja2.txt +13 -0
  119. package/apps/jsmdcui/tests/syntax-parity/samples/json.txt +21 -0
  120. package/apps/jsmdcui/tests/syntax-parity/samples/jsonnet.txt +28 -0
  121. package/apps/jsmdcui/tests/syntax-parity/samples/julia.txt +24 -0
  122. package/apps/jsmdcui/tests/syntax-parity/samples/justfile.txt +26 -0
  123. package/apps/jsmdcui/tests/syntax-parity/samples/keymap.txt +15 -0
  124. package/apps/jsmdcui/tests/syntax-parity/samples/kickstart.txt +16 -0
  125. package/apps/jsmdcui/tests/syntax-parity/samples/kotlin.txt +30 -0
  126. package/apps/jsmdcui/tests/syntax-parity/samples/kvlang.txt +37 -0
  127. package/apps/jsmdcui/tests/syntax-parity/samples/ledger.txt +13 -0
  128. package/apps/jsmdcui/tests/syntax-parity/samples/lfe.txt +17 -0
  129. package/apps/jsmdcui/tests/syntax-parity/samples/lilypond.txt +15 -0
  130. package/apps/jsmdcui/tests/syntax-parity/samples/lisp.txt +17 -0
  131. package/apps/jsmdcui/tests/syntax-parity/samples/log.txt +45 -0
  132. package/apps/jsmdcui/tests/syntax-parity/samples/lua.txt +48 -0
  133. package/apps/jsmdcui/tests/syntax-parity/samples/mail.txt +17 -0
  134. package/apps/jsmdcui/tests/syntax-parity/samples/makefile.txt +25 -0
  135. package/apps/jsmdcui/tests/syntax-parity/samples/man.txt +12 -0
  136. package/apps/jsmdcui/tests/syntax-parity/samples/markdown.txt +21 -0
  137. package/apps/jsmdcui/tests/syntax-parity/samples/mc.txt +11 -0
  138. package/apps/jsmdcui/tests/syntax-parity/samples/meson.txt +29 -0
  139. package/apps/jsmdcui/tests/syntax-parity/samples/micro.txt +25 -0
  140. package/apps/jsmdcui/tests/syntax-parity/samples/mpdconf.txt +13 -0
  141. package/apps/jsmdcui/tests/syntax-parity/samples/msbuild.txt +7 -0
  142. package/apps/jsmdcui/tests/syntax-parity/samples/nanorc.txt +16 -0
  143. package/apps/jsmdcui/tests/syntax-parity/samples/nftables.txt +22 -0
  144. package/apps/jsmdcui/tests/syntax-parity/samples/nginx.txt +17 -0
  145. package/apps/jsmdcui/tests/syntax-parity/samples/nim.txt +23 -0
  146. package/apps/jsmdcui/tests/syntax-parity/samples/nix.txt +15 -0
  147. package/apps/jsmdcui/tests/syntax-parity/samples/nu.txt +92 -0
  148. package/apps/jsmdcui/tests/syntax-parity/samples/objc.txt +36 -0
  149. package/apps/jsmdcui/tests/syntax-parity/samples/ocaml.txt +25 -0
  150. package/apps/jsmdcui/tests/syntax-parity/samples/octave.txt +35 -0
  151. package/apps/jsmdcui/tests/syntax-parity/samples/odin.txt +35 -0
  152. package/apps/jsmdcui/tests/syntax-parity/samples/pascal.txt +22 -0
  153. package/apps/jsmdcui/tests/syntax-parity/samples/patch.txt +13 -0
  154. package/apps/jsmdcui/tests/syntax-parity/samples/peg.txt +16 -0
  155. package/apps/jsmdcui/tests/syntax-parity/samples/perl.txt +27 -0
  156. package/apps/jsmdcui/tests/syntax-parity/samples/php.txt +44 -0
  157. package/apps/jsmdcui/tests/syntax-parity/samples/pkg-config.txt +12 -0
  158. package/apps/jsmdcui/tests/syntax-parity/samples/po.txt +12 -0
  159. package/apps/jsmdcui/tests/syntax-parity/samples/pony.txt +29 -0
  160. package/apps/jsmdcui/tests/syntax-parity/samples/pov.txt +17 -0
  161. package/apps/jsmdcui/tests/syntax-parity/samples/privoxy-action.txt +14 -0
  162. package/apps/jsmdcui/tests/syntax-parity/samples/privoxy-config.txt +10 -0
  163. package/apps/jsmdcui/tests/syntax-parity/samples/privoxy-filter.txt +12 -0
  164. package/apps/jsmdcui/tests/syntax-parity/samples/proto.txt +22 -0
  165. package/apps/jsmdcui/tests/syntax-parity/samples/prql.txt +42 -0
  166. package/apps/jsmdcui/tests/syntax-parity/samples/puppet.txt +22 -0
  167. package/apps/jsmdcui/tests/syntax-parity/samples/python2.txt +24 -0
  168. package/apps/jsmdcui/tests/syntax-parity/samples/python3.txt +28 -0
  169. package/apps/jsmdcui/tests/syntax-parity/samples/r.txt +16 -0
  170. package/apps/jsmdcui/tests/syntax-parity/samples/raku.txt +19 -0
  171. package/apps/jsmdcui/tests/syntax-parity/samples/reST.txt +18 -0
  172. package/apps/jsmdcui/tests/syntax-parity/samples/renpy.txt +11 -0
  173. package/apps/jsmdcui/tests/syntax-parity/samples/rpmspec.txt +31 -0
  174. package/apps/jsmdcui/tests/syntax-parity/samples/ruby.txt +34 -0
  175. package/apps/jsmdcui/tests/syntax-parity/samples/rust.txt +27 -0
  176. package/apps/jsmdcui/tests/syntax-parity/samples/sage.txt +24 -0
  177. package/apps/jsmdcui/tests/syntax-parity/samples/scad.txt +23 -0
  178. package/apps/jsmdcui/tests/syntax-parity/samples/scala.txt +16 -0
  179. package/apps/jsmdcui/tests/syntax-parity/samples/sed.txt +12 -0
  180. package/apps/jsmdcui/tests/syntax-parity/samples/sh.txt +20 -0
  181. package/apps/jsmdcui/tests/syntax-parity/samples/sls.txt +15 -0
  182. package/apps/jsmdcui/tests/syntax-parity/samples/smalltalk.txt +28 -0
  183. package/apps/jsmdcui/tests/syntax-parity/samples/solidity.txt +26 -0
  184. package/apps/jsmdcui/tests/syntax-parity/samples/sql.txt +35 -0
  185. package/apps/jsmdcui/tests/syntax-parity/samples/stata.txt +32 -0
  186. package/apps/jsmdcui/tests/syntax-parity/samples/svelte.txt +10 -0
  187. package/apps/jsmdcui/tests/syntax-parity/samples/swift.txt +53 -0
  188. package/apps/jsmdcui/tests/syntax-parity/samples/systemd.txt +15 -0
  189. package/apps/jsmdcui/tests/syntax-parity/samples/tcl.txt +17 -0
  190. package/apps/jsmdcui/tests/syntax-parity/samples/terraform.txt +28 -0
  191. package/apps/jsmdcui/tests/syntax-parity/samples/tex.txt +15 -0
  192. package/apps/jsmdcui/tests/syntax-parity/samples/toml.txt +31 -0
  193. package/apps/jsmdcui/tests/syntax-parity/samples/twig.txt +29 -0
  194. package/apps/jsmdcui/tests/syntax-parity/samples/typescript.txt +32 -0
  195. package/apps/jsmdcui/tests/syntax-parity/samples/v.txt +47 -0
  196. package/apps/jsmdcui/tests/syntax-parity/samples/vala.txt +22 -0
  197. package/apps/jsmdcui/tests/syntax-parity/samples/verilog.txt +37 -0
  198. package/apps/jsmdcui/tests/syntax-parity/samples/vhdl.txt +37 -0
  199. package/apps/jsmdcui/tests/syntax-parity/samples/vi.txt +17 -0
  200. package/apps/jsmdcui/tests/syntax-parity/samples/vue.txt +29 -0
  201. package/apps/jsmdcui/tests/syntax-parity/samples/xml.txt +14 -0
  202. package/apps/jsmdcui/tests/syntax-parity/samples/xresources.txt +14 -0
  203. package/apps/jsmdcui/tests/syntax-parity/samples/yaml.txt +19 -0
  204. package/apps/jsmdcui/tests/syntax-parity/samples/yum.txt +12 -0
  205. package/apps/jsmdcui/tests/syntax-parity/samples/zig.txt +24 -0
  206. package/apps/jsmdcui/tests/syntax-parity/samples/zscript.txt +31 -0
  207. package/apps/jsmdcui/tests/syntax-parity/samples/zsh.txt +21 -0
  208. package/package.json +1 -1
@@ -12,7 +12,10 @@ const crypto = require("crypto");
12
12
  const zlib = require("zlib");
13
13
  const { constants: fsConstants } = require("fs");
14
14
  const WebSocket = require("ws");
15
+ const { TerminalModeTracker } = require("./terminal-modes.js");
15
16
  const { WebSocketServer } = WebSocket;
17
+ const { ClipboardHost, handleOSC52 } = require("./clipboard.js");
18
+ const clipboardHost = new ClipboardHost();
16
19
 
17
20
  const MSG_INPUT = "1";
18
21
  const MSG_PING = "2";
@@ -34,6 +37,7 @@ const MSG_ZMODEM_MODE = "z";
34
37
  const MSG_ZMODEM_OUTPUT = "y";
35
38
  const MSG_ZMODEM_ACK = "Y";
36
39
  const MSG_KITTY_DEBUG = "K";
40
+ const MSG_RESTORE_MODES = "m";
37
41
  const KITTY_TRACE = /^(1|true|yes|on)$/i.test(String(process.env.JSGOTTY_KITTY_DEBUG || ""));
38
42
  const WINDOWS_BRIDGE_TRACE = process.env.WINDOWS_BRIDGE_TRACE === "1";
39
43
  const WINDOWS_BRIDGE_PREFIX = Buffer.from("@@GOTTYCTL:", "ascii");
@@ -1680,6 +1684,7 @@ class PtySession {
1680
1684
  this.supportsWindowsBridge = process.platform === "win32";
1681
1685
  this.cursorTracker = new CursorStateTracker();
1682
1686
  this.cursorTracker.setDimensions(this.columns || 80);
1687
+ this.modeTracker = new TerminalModeTracker();
1683
1688
  this.kittyParser = new KittyGraphicsParser();
1684
1689
  this.windowsBridgeParser = this.supportsWindowsBridge ? new WindowsBridgeParser("incoming") : null;
1685
1690
  this.outgoingWindowsBridgeParser = this.supportsWindowsBridge ? new WindowsBridgeParser("outgoing") : null;
@@ -1759,6 +1764,7 @@ class PtySession {
1759
1764
  if (!this.supportsWindowsBridge) {
1760
1765
  const cursorBefore = this.cursorTracker.snapshot();
1761
1766
  this.cursorTracker.consume(filteredChunk);
1767
+ this.modeTracker.consume(filteredChunk);
1762
1768
  const cursorAfter = this.cursorTracker.snapshot();
1763
1769
  const controls = cursorControls(filteredChunk);
1764
1770
  if (controls.length > 0) {
@@ -1776,6 +1782,7 @@ class PtySession {
1776
1782
  if (bridged.plain.length > 0) {
1777
1783
  const cursorBefore = this.cursorTracker.snapshot();
1778
1784
  this.cursorTracker.consume(bridged.plain);
1785
+ this.modeTracker.consume(bridged.plain);
1779
1786
  const cursorAfter = this.cursorTracker.snapshot();
1780
1787
  const controls = cursorControls(bridged.plain);
1781
1788
  if (controls.length > 0) {
@@ -1855,6 +1862,9 @@ class PtySession {
1855
1862
  this.writerWs = ws;
1856
1863
  }
1857
1864
  this.sendSessionMetadata(ws, remoteAddr);
1865
+ // Queued ahead of live output; the client applies only modes that differ,
1866
+ // which also clears modes a program turned off while the socket was down.
1867
+ this.send(MSG_RESTORE_MODES, JSON.stringify(this.modeTracker.snapshot()), ws);
1858
1868
  this.startWebSocketHandlers(ws);
1859
1869
  this.notifyRoles();
1860
1870
  return true;
@@ -2442,6 +2452,9 @@ class PtySession {
2442
2452
  }
2443
2453
 
2444
2454
  switch (type) {
2455
+ case "C":
2456
+ handleOSC52(this, ws, payload, clipboardHost);
2457
+ break;
2445
2458
  case MSG_INPUT:
2446
2459
  if (!this.isWriter(ws) || payload.length === 0) {
2447
2460
  return;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jsgotty",
9
- "version": "1.2.0",
9
+ "version": "1.4.0",
10
10
  "dependencies": {
11
11
  "node-addon-api": "^7.1.1",
12
12
  "ws": "^8.18.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Share your terminal as a web application: A JavaScript rewrite of GoTTY",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -10,4 +10,17 @@ html, body, #terminal {
10
10
  transition: unset;
11
11
  transition-duration: 0.1s;
12
12
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
13
- }
13
+ }
14
+
15
+ /* Keep touch taps available to xterm even with a widened WebView viewport.
16
+ Include the viewport (a scroll container); preserve panning and pinch zoom. */
17
+ #terminal, #terminal .xterm, #terminal .xterm-viewport, #terminal .xterm-screen {
18
+ touch-action: manipulation;
19
+ }
20
+
21
+ /* Chromium skips focused-editable auto-zoom when that element disallows
22
+ pinch zoom. Restrict only the hidden input, not the terminal canvas:
23
+ native paste/IME remain available and the visible terminal can still pinch. */
24
+ #terminal .xterm-helper-textarea {
25
+ touch-action: none;
26
+ }
@@ -12168,7 +12168,8 @@
12168
12168
  (t.msgSetWindowTitle = "3"),
12169
12169
  (t.msgSetPreferences = "4"),
12170
12170
  (t.msgSetReconnect = "5"),
12171
- (t.msgSetBufferSize = "6"));
12171
+ (t.msgSetBufferSize = "6"),
12172
+ (t.msgRestoreModes = "m"));
12172
12173
  t.WebTTY = class {
12173
12174
  term;
12174
12175
  connectionFactory;
@@ -12193,6 +12194,9 @@
12193
12194
  this.reconnectDeadline = 0;
12194
12195
  this.reconnectAttempts = 0;
12195
12196
  this.zmodemExpectedSeq = 0;
12197
+ this.term.onClipboard?.(data => {
12198
+ if (!this.disconnected) this.connection.send("C" + data);
12199
+ });
12196
12200
  }
12197
12201
  open() {
12198
12202
  let e;
@@ -12330,6 +12334,9 @@
12330
12334
  const e = JSON.parse(i);
12331
12335
  this.term.setPreferences(e);
12332
12336
  break;
12337
+ case t.msgRestoreModes:
12338
+ this.term.applySessionModes(JSON.parse(i));
12339
+ break;
12333
12340
  case t.msgSetReconnect:
12334
12341
  const r = JSON.parse(i);
12335
12342
  "number" == typeof r
@@ -12540,12 +12547,30 @@
12540
12547
  })),
12541
12548
  (this.fitAddOn = new s.FitAddon()),
12542
12549
  (this.zmodemAddon = new a.ZModemAddon({
12543
- toTerminal: (e) => this.term.write(e),
12550
+ toTerminal: (e) => this.writeTerminal(e),
12544
12551
  toServer: (e) => this.sendInput(e),
12545
12552
  })),
12546
12553
  (this.kittyKeyboard = new globalThis.GoTTYKittyKeyboard.KittyKeyboard(this.term, {
12547
12554
  android: /Android/i.test(navigator.userAgent),
12548
12555
  })),
12556
+ this.term.parser.registerOscHandler(52, data => {
12557
+ if (!this.disconnected && data.length <= 1048576) this.clipboardHandler?.(data);
12558
+ return true;
12559
+ }),
12560
+ this.term.parser.registerOscHandler(777, data => {
12561
+ const match = /^jsgotty-modes;([a-f0-9]{32})$/.exec(data);
12562
+ if (!match) {
12563
+ const restore = /^jsgotty-kitty-v1;(normal|alternate);(\d{1,2});(\d{1,2}(?:,\d{1,2})*)?$/.exec(data);
12564
+ if (!restore) return false;
12565
+ const flags = +restore[2], stack = restore[3] ? restore[3].split(',').map(Number) : [];
12566
+ if (flags > 31 || stack.length > 32 || stack.some(n => n > 31)) return true;
12567
+ this.kittyKeyboard.states[restore[1]] = {flags: flags & 11, stack: stack.map(n => n & 11)};
12568
+ return true;
12569
+ }
12570
+ this.term.input("\x1b]777;jsgotty-modes;" + match[1] + ";" +
12571
+ btoa(JSON.stringify({...this.sessionModes(), kittyRestore: 1})) + "\x07", false);
12572
+ return true;
12573
+ }),
12549
12574
  this.term.attachCustomKeyEventHandler((event) =>
12550
12575
  this.kittyKeyboard.handle(event) && this.handleAndroidAltKey(event),
12551
12576
  ),
@@ -12676,8 +12701,73 @@
12676
12701
  getMessage() {
12677
12702
  return this.message;
12678
12703
  }
12704
+ enqueueTerminal(task) {
12705
+ this.terminalTasks ||= [];
12706
+ this.terminalTasks.push(task);
12707
+ if (this.terminalBusy) return;
12708
+ this.terminalBusy = true;
12709
+ const next = () => {
12710
+ const task = this.terminalTasks.shift();
12711
+ if (!task) { this.terminalBusy = false; return; }
12712
+ task(next);
12713
+ };
12714
+ next();
12715
+ }
12716
+ writeTerminal(data) {
12717
+ this.enqueueTerminal(done => this.term.write(data, done));
12718
+ }
12719
+ onClipboard(handler) {
12720
+ this.clipboardHandler = handler;
12721
+ }
12722
+ sessionModes() {
12723
+ const core = this.term._core;
12724
+ return {dec: {...core.coreService.decPrivateModes},
12725
+ mouse: core.coreMouseService.activeProtocol,
12726
+ encoding: core.coreMouseService.activeEncoding,
12727
+ cursorHidden: !!core.coreService.isCursorHidden,
12728
+ alternate: this.term.buffer.active.type === "alternate",
12729
+ kitty: JSON.parse(JSON.stringify(this.kittyKeyboard.states))};
12730
+ }
12731
+ applySessionModes(state) {
12732
+ this.enqueueTerminal(done => this.term.write("", () => this.restoreSessionModes(state, done)));
12733
+ }
12734
+ restoreSessionModes(state, done) {
12735
+ if (!state || !state.dec) { done(); return; }
12736
+ const core = this.term._core;
12737
+ const dec = core.coreService.decPrivateModes;
12738
+ const mouse = core.coreMouseService;
12739
+ // A socket-only reconnect keeps xterm's modes, so send only differences:
12740
+ // DECOM homes the cursor and 1049 overwrites the saved cursor.
12741
+ let sequence = "";
12742
+ const alternate = this.term.buffer.active.type === "alternate";
12743
+ if (!!state.alternate !== alternate) sequence += `\x1b[?1049${state.alternate ? "h" : "l"}`;
12744
+ const flags = {1:"applicationCursorKeys", 6:"origin", 7:"wraparound", 45:"reverseWraparound",
12745
+ 66:"applicationKeypad", 1004:"sendFocus", 2004:"bracketedPasteMode"};
12746
+ for (const [mode, name] of Object.entries(flags))
12747
+ if (!!state.dec[name] !== !!dec[name]) sequence += `\x1b[?${mode}${state.dec[name] ? "h" : "l"}`;
12748
+ if (!!state.cursorHidden !== !!core.coreService.isCursorHidden)
12749
+ sequence += `\x1b[?25${state.cursorHidden ? "l" : "h"}`;
12750
+ if (state.mouse !== mouse.activeProtocol) {
12751
+ const modes = {X10:9, VT200:1000, DRAG:1002, ANY:1003};
12752
+ sequence += modes[state.mouse] ? `\x1b[?${modes[state.mouse]}h` : "\x1b[?1000l";
12753
+ }
12754
+ if (state.encoding !== mouse.activeEncoding) {
12755
+ const modes = {SGR:1006, SGR_PIXELS:1016};
12756
+ sequence += modes[state.encoding] ? `\x1b[?${modes[state.encoding]}h` : "\x1b[?1006l";
12757
+ }
12758
+ const complete = () => {
12759
+ // Assigned rather than replayed so the push stack never doubles; the server
12760
+ // keeps every flag, so mask to the ones kitty-keyboard.js implements.
12761
+ const supported = (s) => ({flags: s.flags & 11, stack: s.stack.map((f) => f & 11)});
12762
+ if (state.kitty?.normal && state.kitty?.alternate)
12763
+ this.kittyKeyboard.states = {normal: supported(state.kitty.normal), alternate: supported(state.kitty.alternate)};
12764
+ done();
12765
+ };
12766
+ if (sequence) this.term.write(sequence, complete);
12767
+ else complete();
12768
+ }
12679
12769
  setDisconnected(e) {
12680
- if (e) this.kittyKeyboard.reset();
12770
+ if (e) this.kittyKeyboard.clearHeld(false);
12681
12771
  this.disconnected = !!e;
12682
12772
  }
12683
12773
  setReconnectAction(e) {
@@ -12789,7 +12879,8 @@
12789
12879
  this.term.blur();
12790
12880
  }
12791
12881
  reset() {
12792
- this.kittyKeyboard.reset();
12882
+ // Reconnecting the transport does not reset the running PTY's modes.
12883
+ this.kittyKeyboard.clearHeld(false);
12793
12884
  (this.removeMessage(), this.term.clear());
12794
12885
  }
12795
12886
  close() {
@@ -15906,8 +15997,15 @@
15906
15997
  !1 === e._customWheelEventHandler(t)
15907
15998
  )
15908
15999
  return !1;
15909
- if (0 === e.viewport.getLinesScrolled(t))
15910
- return !1;
16000
+ // Preserve both axes of diagonal wheel/touch gestures.
16001
+ // CoreMouseService already encodes wheel actions 2/3 as 66/67.
16002
+ if (t.deltaX) e.coreMouseService.triggerMouseEvent({
16003
+ col: i.col, row: i.row, x: i.x, y: i.y,
16004
+ button: 4, action: t.deltaX < 0 ? 2 : 3,
16005
+ ctrl: t.ctrlKey, alt: t.altKey, shift: t.shiftKey,
16006
+ });
16007
+ if (0 === e.viewport.getLinesScrolled(t, true))
16008
+ return !!t.deltaX;
15911
16009
  ((s = t.deltaY < 0 ? 0 : 1), (r = 4));
15912
16010
  break;
15913
16011
  default:
@@ -16881,8 +16979,9 @@
16881
16979
  }
16882
16980
  return { bufferElements: s, cursorElement: i };
16883
16981
  }
16884
- getLinesScrolled(e) {
16885
- if (0 === e.deltaY || e.shiftKey) return 0;
16982
+ getLinesScrolled(e, mouseReporting = false) {
16983
+ // Shift belongs to the remote application while mouse reporting is active.
16984
+ if (0 === e.deltaY || (e.shiftKey && !mouseReporting)) return 0;
16886
16985
  let t = this._applyScrollModifier(e.deltaY, e);
16887
16986
  return (
16888
16987
  e.deltaMode === WheelEvent.DOM_DELTA_PIXEL
@@ -561,6 +561,26 @@
561
561
  state.renderBound = state.renderDisposables.length > 0;
562
562
  }
563
563
 
564
+ // Touch gestures report mouse input without DOM mousedown: xterm's DOM
565
+ // handler focuses its hidden textarea, which can zoom Android WebView.
566
+ function reportTouchMouse(type, point, button = 0) {
567
+ const core = terminal?.__gottyXterm?._core;
568
+ if (!core?.coreMouseService?.areMouseEventsActive) return;
569
+ const screen = core.screenElement;
570
+ if (!screen) return;
571
+ const rect = screen.getBoundingClientRect();
572
+ const coords = core._mouseService.getMouseReportCoords({
573
+ clientX: Math.max(rect.left, Math.min(rect.right - 1, point.clientX)),
574
+ clientY: Math.max(rect.top, Math.min(rect.bottom - 1, point.clientY)),
575
+ }, screen);
576
+ if (!coords) return;
577
+ core.coreMouseService.triggerMouseEvent({
578
+ ...coords, button,
579
+ action: type === "mousedown" ? 1 : type === "mousemove" ? 32 : 0,
580
+ ctrl: false, alt: false, shift: false,
581
+ });
582
+ }
583
+
564
584
  function dispatchSyntheticWheel(target, touch, deltaX, deltaY) {
565
585
  if (!target) {
566
586
  return;
@@ -597,22 +617,69 @@
597
617
  return screen || viewport || xterm || terminal;
598
618
  };
599
619
 
620
+ let lastTap = null;
621
+ let drag = null;
622
+ const mouse = (type, point) => reportTouchMouse(type, point);
623
+ const releaseDrag = () => {
624
+ if (drag) {
625
+ const point = drag;
626
+ drag = null;
627
+ mouse("mouseup", point);
628
+ }
629
+ };
630
+ const cancelGesture = () => {
631
+ releaseDrag();
632
+ lastTap = null;
633
+ state.touchScroll = null;
634
+ };
635
+ state.cancelTouchGesture = cancelGesture;
636
+ bindTarget.addEventListener("contextmenu", (event) => {
637
+ if (drag) { event.preventDefault(); event.stopImmediatePropagation(); }
638
+ }, true);
639
+ window.addEventListener("blur", cancelGesture);
640
+ document.addEventListener("visibilitychange", () => {
641
+ if (document.hidden) cancelGesture();
642
+ });
643
+
600
644
  bindTarget.addEventListener("touchstart", (event) => {
601
645
  if (event.touches.length !== 1) {
602
- state.touchScroll = null;
646
+ cancelGesture();
603
647
  return;
604
648
  }
605
649
  const touch = event.touches[0];
650
+ const reporting = terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive;
651
+ if (reporting && window.touchDoubleTapDrag !== false && lastTap &&
652
+ performance.now() - lastTap.time < 350 &&
653
+ Math.hypot(touch.clientX - lastTap.x, touch.clientY - lastTap.y) < 24) {
654
+ lastTap = null;
655
+ state.touchScroll = null;
656
+ drag = { identifier: touch.identifier, clientX: touch.clientX, clientY: touch.clientY };
657
+ // Cancel compatibility mouse events: this is the second press, not a third click.
658
+ event.preventDefault();
659
+ mouse("mousedown", drag);
660
+ return;
661
+ }
662
+ lastTap = null;
606
663
  state.touchScroll = {
664
+ startX: touch.clientX, startY: touch.clientY, startTime: performance.now(), moved: false,
607
665
  identifier: touch.identifier,
608
666
  lastX: touch.clientX,
609
667
  lastY: touch.clientY,
610
668
  residualX: 0,
611
669
  residualY: 0
612
670
  };
613
- }, { passive: true });
671
+ }, { passive: false });
614
672
 
615
673
  bindTarget.addEventListener("touchmove", (event) => {
674
+ if (drag) {
675
+ const touch = Array.from(event.touches).find(t => t.identifier === drag.identifier);
676
+ if (!touch || event.touches.length !== 1) { cancelGesture(); return; }
677
+ event.preventDefault();
678
+ drag.clientX = touch.clientX;
679
+ drag.clientY = touch.clientY;
680
+ mouse("mousemove", drag);
681
+ return;
682
+ }
616
683
  if (!state.touchScroll) {
617
684
  return;
618
685
  }
@@ -621,6 +688,8 @@
621
688
  return;
622
689
  }
623
690
 
691
+ if (Math.hypot(touch.clientX - state.touchScroll.startX,
692
+ touch.clientY - state.touchScroll.startY) > 8) state.touchScroll.moved = true;
624
693
  const deltaX = state.touchScroll.lastX - touch.clientX;
625
694
  const deltaY = state.touchScroll.lastY - touch.clientY;
626
695
  state.touchScroll.lastX = touch.clientX;
@@ -653,11 +722,20 @@
653
722
  state.touchScroll.residualY -= ticksY * threshold * stepY;
654
723
  }, { passive: false });
655
724
 
656
- const clearTouchScroll = () => {
725
+ bindTarget.addEventListener("touchend", (event) => {
726
+ if (drag) {
727
+ event.preventDefault();
728
+ releaseDrag();
729
+ } else {
730
+ const scroll = state.touchScroll;
731
+ if (scroll && !scroll.moved && event.touches.length === 0 &&
732
+ performance.now() - scroll.startTime < 350) {
733
+ lastTap = { x: scroll.lastX, y: scroll.lastY, time: performance.now() };
734
+ }
735
+ }
657
736
  state.touchScroll = null;
658
- };
659
- bindTarget.addEventListener("touchend", clearTouchScroll, { passive: true });
660
- bindTarget.addEventListener("touchcancel", clearTouchScroll, { passive: true });
737
+ }, { passive: false });
738
+ bindTarget.addEventListener("touchcancel", cancelGesture, { passive: true });
661
739
  state.touchBound = true;
662
740
  }
663
741
 
@@ -676,9 +754,25 @@
676
754
  removeButton.textContent = "Remove image";
677
755
  menu.appendChild(removeButton);
678
756
 
757
+ const separator = document.createElement("hr");
758
+ const rightButton = document.createElement("button");
759
+ rightButton.type = "button";
760
+ rightButton.className = "kitty-context-menu__item";
761
+ rightButton.textContent = "Right click";
762
+ menu.append(separator, rightButton);
763
+ rightButton.addEventListener("click", (event) => {
764
+ event.preventDefault();
765
+ event.stopPropagation();
766
+ const point = state.contextMenu?.point;
767
+ hideContextMenu();
768
+ if (!point || !terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive) return;
769
+ reportTouchMouse("mousedown", { clientX: point.x, clientY: point.y }, 2);
770
+ reportTouchMouse("mouseup", { clientX: point.x, clientY: point.y }, 2);
771
+ });
772
+
679
773
  state.contextMenu = {
680
774
  root: menu,
681
- removeButton,
775
+ removeButton, rightButton, separator,
682
776
  targetKey: null
683
777
  };
684
778
 
@@ -722,6 +816,9 @@
722
816
  function showContextMenu(x, y, key) {
723
817
  const menu = ensureContextMenu();
724
818
  menu.targetKey = key;
819
+ menu.point = { x, y };
820
+ const reporting = !!terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive;
821
+ menu.rightButton.hidden = menu.separator.hidden = !reporting;
725
822
  menu.root.hidden = false;
726
823
 
727
824
  const margin = 10;
@@ -1004,6 +1101,15 @@
1004
1101
  return false;
1005
1102
  }
1006
1103
 
1104
+ const watchedSockets = new WeakSet();
1105
+ function watchTouchSocket(socket) {
1106
+ if (watchedSockets.has(socket)) return;
1107
+ watchedSockets.add(socket);
1108
+ socket.addEventListener("close", () => {
1109
+ if (state.socket === socket) state.cancelTouchGesture?.();
1110
+ });
1111
+ }
1112
+
1007
1113
  const nativeSend = WebSocket.prototype.send;
1008
1114
  const nativeOnOpen = WebSocket.prototype.addEventListener;
1009
1115
  WebSocket.prototype.addEventListener = function (type, listener, options) {
@@ -1012,6 +1118,7 @@
1012
1118
  }
1013
1119
  const wrapped = function (event) {
1014
1120
  state.socket = this;
1121
+ watchTouchSocket(this);
1015
1122
  inspectTerminalOutput(event.data);
1016
1123
  if (typeof event.data === "string" && event.data[0] === MSG_KITTY) {
1017
1124
  if (handleKittyMessage(event.data.slice(1))) {
@@ -1058,6 +1165,7 @@
1058
1165
  }
1059
1166
  const wrapped = function (event) {
1060
1167
  state.socket = this;
1168
+ watchTouchSocket(this);
1061
1169
  inspectTerminalOutput(event.data);
1062
1170
  if (typeof event.data === "string" && event.data[0] === MSG_KITTY) {
1063
1171
  if (handleKittyMessage(event.data.slice(1))) {