buninu-linux 0.15.58 → 0.17.60

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 (228) hide show
  1. package/NOTICE.md +46 -46
  2. package/README.md +22 -15
  3. package/README.zh.md +22 -15
  4. package/initramfs/buninu/CHANGELOG.md +47 -0
  5. package/initramfs/buninu/apps/jsgotty/LICENSES/MIT-jsmdcui.txt +22 -0
  6. package/initramfs/buninu/apps/jsgotty/NOTICE +5 -0
  7. package/initramfs/buninu/apps/jsgotty/README.en.md +21 -0
  8. package/initramfs/buninu/apps/jsgotty/README.md +21 -0
  9. package/initramfs/buninu/apps/jsgotty/client-modes.mjs +117 -0
  10. package/initramfs/buninu/apps/jsgotty/client.mjs +100 -10
  11. package/initramfs/buninu/apps/jsgotty/clipboard-powershell.js +18 -0
  12. package/initramfs/buninu/apps/jsgotty/clipboard.js +148 -0
  13. package/initramfs/buninu/apps/jsgotty/gotty.js +13 -0
  14. package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
  15. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  16. package/initramfs/buninu/apps/jsgotty/static/css/index.css +14 -1
  17. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +107 -8
  18. package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +115 -7
  19. package/initramfs/buninu/apps/jsgotty/terminal-modes.js +332 -0
  20. package/initramfs/buninu/apps/jsgotty/tests/client-exit.test.mjs +45 -0
  21. package/initramfs/buninu/apps/jsgotty/tests/client-modes.test.mjs +105 -0
  22. package/initramfs/buninu/apps/jsgotty/tests/clipboard-browser.test.js +81 -0
  23. package/initramfs/buninu/apps/jsgotty/tests/clipboard.test.js +75 -0
  24. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-collector.cjs +55 -0
  25. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-debug.js +39 -0
  26. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.js +33 -0
  27. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.mjs +22 -0
  28. package/initramfs/buninu/apps/jsgotty/tests/nested-cli-pty.test.mjs +116 -0
  29. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes-browser.test.js +144 -0
  30. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.md +44 -0
  31. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.test.js +166 -0
  32. package/initramfs/buninu/apps/jsgotty/tests/touch-drag-browser.test.js +106 -0
  33. package/initramfs/buninu/apps/jsgotty/tests/wheel-browser.test.js +60 -0
  34. package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +44 -0
  35. package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
  36. package/initramfs/buninu/apps/jsmdcui/src/highlight/go-regexp.js +83 -0
  37. package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +49 -62
  38. package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +8 -5
  39. package/initramfs/buninu/apps/jsmdcui/tests/go-regexp.test.js +98 -0
  40. package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +21 -16
  41. package/initramfs/buninu/apps/jsmdcui/tests/highlight-regions.test.js +73 -0
  42. package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +32 -9
  43. package/initramfs/buninu/apps/jsmdcui/tests/markdown-behavior.mjs +9 -0
  44. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/NORMAL-HTML.md +46 -0
  45. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/README.md +85 -0
  46. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/REPORT.md +328 -0
  47. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/compare.mjs +75 -0
  48. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/go.mod +5 -0
  49. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html-report.json +619 -0
  50. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html.html +9 -0
  51. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/oracle.go +49 -0
  52. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/report.json +2174 -0
  53. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/PowerShell.txt +39 -0
  54. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ada.txt +25 -0
  55. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/apacheconf.txt +49 -0
  56. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/arduino.txt +38 -0
  57. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asciidoc.txt +33 -0
  58. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asm.txt +77 -0
  59. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ats.txt +48 -0
  60. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/awk.txt +28 -0
  61. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/b.txt +27 -0
  62. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/bat.txt +39 -0
  63. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/c.txt +34 -0
  64. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/caddyfile.txt +14 -0
  65. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cake.txt +7 -0
  66. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/clojure.txt +18 -0
  67. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cmake.txt +20 -0
  68. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/coffeescript.txt +32 -0
  69. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/colortest.txt +19 -0
  70. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/conky.txt +12 -0
  71. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cpp.txt +46 -0
  72. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crontab.txt +16 -0
  73. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crystal.txt +28 -0
  74. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csharp.txt +28 -0
  75. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/css.txt +19 -0
  76. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csx.txt +7 -0
  77. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cuda.txt +37 -0
  78. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cython.txt +22 -0
  79. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/d.txt +52 -0
  80. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dart.txt +27 -0
  81. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/default.txt +8 -0
  82. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dockerfile.txt +10 -0
  83. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dot.txt +15 -0
  84. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elixir.txt +23 -0
  85. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elm.txt +20 -0
  86. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erb.txt +32 -0
  87. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erlang.txt +23 -0
  88. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fish.txt +20 -0
  89. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/forth.txt +17 -0
  90. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fortran.txt +40 -0
  91. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/freebsd-kernel.txt +10 -0
  92. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fsharp.txt +27 -0
  93. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gdscript.txt +26 -0
  94. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gemini.txt +11 -0
  95. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-ebuild.txt +28 -0
  96. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-etc-portage.txt +14 -0
  97. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-commit.txt +19 -0
  98. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-config.txt +11 -0
  99. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-rebase-todo.txt +10 -0
  100. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gleam.txt +44 -0
  101. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/glsl.txt +18 -0
  102. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gnuplot.txt +11 -0
  103. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/go.txt +33 -0
  104. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/godoc.txt +9 -0
  105. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/golo.txt +50 -0
  106. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gomod.txt +16 -0
  107. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/graphql.txt +20 -0
  108. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groff.txt +18 -0
  109. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groovy.txt +42 -0
  110. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haml.txt +16 -0
  111. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hare.txt +31 -0
  112. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haskell.txt +26 -0
  113. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hc.txt +28 -0
  114. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html.txt +26 -0
  115. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html4.txt +20 -0
  116. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html5.txt +20 -0
  117. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ini.txt +16 -0
  118. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/inputrc.txt +14 -0
  119. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/java.txt +18 -0
  120. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/javascript.txt +41 -0
  121. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jinja2.txt +13 -0
  122. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/json.txt +21 -0
  123. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jsonnet.txt +28 -0
  124. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/julia.txt +24 -0
  125. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/justfile.txt +26 -0
  126. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/keymap.txt +15 -0
  127. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kickstart.txt +16 -0
  128. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kotlin.txt +30 -0
  129. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kvlang.txt +37 -0
  130. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ledger.txt +13 -0
  131. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lfe.txt +17 -0
  132. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lilypond.txt +15 -0
  133. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lisp.txt +17 -0
  134. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/log.txt +45 -0
  135. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lua.txt +48 -0
  136. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mail.txt +17 -0
  137. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/makefile.txt +25 -0
  138. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/man.txt +12 -0
  139. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/markdown.txt +21 -0
  140. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mc.txt +11 -0
  141. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/meson.txt +29 -0
  142. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/micro.txt +25 -0
  143. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mpdconf.txt +13 -0
  144. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/msbuild.txt +7 -0
  145. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nanorc.txt +16 -0
  146. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nftables.txt +22 -0
  147. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nginx.txt +17 -0
  148. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nim.txt +23 -0
  149. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nix.txt +15 -0
  150. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nu.txt +92 -0
  151. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/objc.txt +36 -0
  152. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ocaml.txt +25 -0
  153. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/octave.txt +35 -0
  154. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/odin.txt +35 -0
  155. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pascal.txt +22 -0
  156. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/patch.txt +13 -0
  157. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/peg.txt +16 -0
  158. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/perl.txt +27 -0
  159. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/php.txt +44 -0
  160. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pkg-config.txt +12 -0
  161. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/po.txt +12 -0
  162. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pony.txt +29 -0
  163. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pov.txt +17 -0
  164. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-action.txt +14 -0
  165. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-config.txt +10 -0
  166. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-filter.txt +12 -0
  167. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/proto.txt +22 -0
  168. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/prql.txt +42 -0
  169. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/puppet.txt +22 -0
  170. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python2.txt +24 -0
  171. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python3.txt +28 -0
  172. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/r.txt +16 -0
  173. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/raku.txt +19 -0
  174. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/reST.txt +18 -0
  175. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/renpy.txt +11 -0
  176. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rpmspec.txt +31 -0
  177. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ruby.txt +34 -0
  178. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rust.txt +27 -0
  179. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sage.txt +24 -0
  180. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scad.txt +23 -0
  181. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scala.txt +16 -0
  182. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sed.txt +12 -0
  183. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sh.txt +20 -0
  184. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sls.txt +15 -0
  185. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/smalltalk.txt +28 -0
  186. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/solidity.txt +26 -0
  187. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sql.txt +35 -0
  188. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/stata.txt +32 -0
  189. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/svelte.txt +10 -0
  190. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/swift.txt +53 -0
  191. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/systemd.txt +15 -0
  192. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tcl.txt +17 -0
  193. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/terraform.txt +28 -0
  194. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tex.txt +15 -0
  195. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/toml.txt +31 -0
  196. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/twig.txt +29 -0
  197. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/typescript.txt +32 -0
  198. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/v.txt +47 -0
  199. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vala.txt +22 -0
  200. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/verilog.txt +37 -0
  201. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vhdl.txt +37 -0
  202. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vi.txt +17 -0
  203. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vue.txt +29 -0
  204. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xml.txt +14 -0
  205. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xresources.txt +14 -0
  206. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yaml.txt +19 -0
  207. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yum.txt +12 -0
  208. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zig.txt +24 -0
  209. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zscript.txt +31 -0
  210. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zsh.txt +21 -0
  211. package/initramfs/buninu/package.json +1 -1
  212. package/initramfs/lib/bunterm/images.js +5 -1
  213. package/initramfs/lib/bunterm/raw-protocol.js +82 -4
  214. package/initramfs/lib/bunterm/renderer.js +35 -16
  215. package/initramfs/lib/bunterm/terminal.js +11 -1
  216. package/initramfs/lib/fbdev.js +11 -5
  217. package/initramfs/lib/mapped-frame.js +28 -0
  218. package/initramfs/lib/raw-image.js +29 -5
  219. package/package.json +1 -1
  220. package/scripts/arch.js +5 -5
  221. package/scripts/arch.sh +5 -5
  222. package/scripts/npm-packages.md +11 -10
  223. package/test/bunterm-image-bytes.test.js +15 -0
  224. package/test/bunterm-mapped-lifetime.test.js +147 -0
  225. package/test/bunterm-raw-protocol.test.js +20 -0
  226. package/test/bunterm-renderer.test.js +9 -0
  227. package/test/fbdev-sync.test.js +9 -0
  228. package/test/fetch-npm.test.js +2 -2
@@ -4,6 +4,10 @@ import process from "node:process";
4
4
  import { Buffer } from "node:buffer";
5
5
  import WebSocket from "ws";
6
6
  import { encodeKittyGraphics, syntheticKittyCursorMotion } from "./client-kitty.mjs";
7
+ import terminalModes from "./terminal-modes.js";
8
+
9
+ const { TerminalModeTracker, modeTransition, validateSnapshot } = terminalModes;
10
+ import { captureTerminalModes } from "./client-modes.mjs";
7
11
 
8
12
  const MSG_INPUT = "1";
9
13
  const MSG_PING = "2";
@@ -19,6 +23,7 @@ const MSG_SET_BUFFER_SIZE = "6";
19
23
  const MSG_KITTY_GRAPHICS = "7";
20
24
  const MSG_SET_ROLE = "a";
21
25
  const MSG_CONTROL_RESULT = "b";
26
+ const MSG_RESTORE_MODES = "m";
22
27
 
23
28
  const DEFAULT_URL = "ws://127.0.0.1:8080/ws";
24
29
 
@@ -409,6 +414,40 @@ async function main() {
409
414
  return;
410
415
  }
411
416
  const args = buildArguments(options, initArguments);
417
+ const originalRawMode = !!process.stdin.isRaw;
418
+ const localModes = new TerminalModeTracker();
419
+ // A closed terminal surfaces as an async EIO/EPIPE, not a throw. Nobody is
420
+ // left to read anything, and the PTY keeps its modes, so just exit.
421
+ for (const stream of [process.stdin, process.stdout, process.stderr]) {
422
+ stream.on("error", (error) => {
423
+ if (error.code !== "EIO" && error.code !== "EPIPE") {
424
+ throw error;
425
+ }
426
+ process.exit(process.exitCode ?? 1);
427
+ });
428
+ }
429
+ const writeTerminal = (data) => {
430
+ process.stdout.write(data);
431
+ localModes.consume(Buffer.from(data));
432
+ };
433
+ const captured = await captureTerminalModes(process.stdin, process.stdout);
434
+ const originalModes = captured.state;
435
+ Object.assign(localModes, validateSnapshot(originalModes));
436
+ let bufferedInput = captured.buffered;
437
+ let probeFlushTimer, replayingProbe = false;
438
+ const terminalInput = chunk => {
439
+ if (replayingProbe) return Buffer.from(chunk);
440
+ const input = captured.probe.consume(chunk);
441
+ clearTimeout(probeFlushTimer);
442
+ // A lone Escape is user input too. Never hold it indefinitely while
443
+ // filtering a split or late response to our startup queries.
444
+ if (captured.probe.pending.length) probeFlushTimer = setTimeout(() => {
445
+ const pending = captured.probe.flush();
446
+ replayingProbe = true;
447
+ try { process.stdin.emit("data", pending); } finally { replayingProbe = false; }
448
+ }, 30);
449
+ return input;
450
+ };
412
451
  writeStatus(`[gotty] connecting to\n ${wsUrl}`);
413
452
  const ws = new WebSocket(wsUrl, "webtty");
414
453
  let pingTimer = null;
@@ -432,7 +471,8 @@ async function main() {
432
471
  if (!writable || ws.readyState !== WebSocket.OPEN) {
433
472
  return;
434
473
  }
435
- ws.send(MSG_INPUT + Buffer.from(chunk).toString("base64"));
474
+ const input = terminalInput(chunk);
475
+ if (input.length) ws.send(MSG_INPUT + input.toString("base64"));
436
476
  };
437
477
 
438
478
  const handleResize = () => {
@@ -441,21 +481,52 @@ async function main() {
441
481
  }
442
482
  };
443
483
 
484
+ let exiting = false;
444
485
  const exitClient = (code = 0) => {
486
+ if (exiting) return;
487
+ exiting = true;
445
488
  cleanup();
446
489
  reportDisconnected();
447
490
  ws.close();
448
- process.exit(code);
491
+ process.stdout.write("", () => process.exit(code));
449
492
  };
450
493
 
494
+ // Restoring the viewed session also restores Kitty keyboard modes. Exit
495
+ // keys can therefore arrive as CSI u rather than legacy control bytes.
496
+ // Keep CSI packets across chunks; never treat a key-release as a press.
497
+ let viewerSequence = "";
451
498
  const handleViewerInput = (chunk) => {
452
- for (const byte of Buffer.from(chunk)) {
453
- if (byte === 0x03) {
454
- exitClient(130);
499
+ for (const byte of terminalInput(chunk)) {
500
+ if (viewerSequence) {
501
+ viewerSequence += String.fromCharCode(byte);
502
+ if (viewerSequence === "\x1b[") continue;
503
+ if (viewerSequence.length === 2 && byte !== 0x5b) {
504
+ viewerSequence = "";
505
+ continue;
506
+ }
507
+ if (viewerSequence.length > 2 && byte >= 0x40 && byte <= 0x7e) {
508
+ const sequence = viewerSequence;
509
+ viewerSequence = "";
510
+ const kitty = /^\x1b\[(\d+)(?::[0-9:]*)?(?:;(\d+)(?::([123]))?)?(?:;[0-9:]+)?u$/.exec(sequence);
511
+ const modified = /^\x1b\[27;(\d+);(\d+)~$/.exec(sequence);
512
+ const key = kitty ? +kitty[1] : modified ? +modified[2] : 0;
513
+ const modifiers = (kitty ? +(kitty[2] || 1) : modified ? +modified[1] : 1) - 1;
514
+ // Ignore Caps/Num Lock, but require Ctrl without Alt/Shift/Super.
515
+ if ((modifiers & ~192) === 4 && (!kitty || kitty[3] !== "3")) {
516
+ if (key === 99) return exitClient(130);
517
+ if (key === 100 || key === 113) return exitClient(0);
518
+ }
519
+ } else if (viewerSequence.length > 128) {
520
+ viewerSequence = "";
521
+ }
522
+ continue;
455
523
  }
456
- if (byte === 0x04 || byte === 0x11) {
457
- exitClient(0);
524
+ if (byte === 0x1b) {
525
+ viewerSequence = "\x1b";
526
+ continue;
458
527
  }
528
+ if (byte === 0x03) return exitClient(130);
529
+ if (byte === 0x04 || byte === 0x11) return exitClient(0);
459
530
  }
460
531
  };
461
532
 
@@ -498,7 +569,8 @@ async function main() {
498
569
  return;
499
570
  }
500
571
 
501
- const input = Buffer.from(chunk);
572
+ const input = terminalInput(chunk);
573
+ if (!input.length) return;
502
574
  fallbackProbe = normalizeEcho(input.toString("utf8"));
503
575
  ws.send(MSG_INPUT + input.toString("base64"));
504
576
  stopFallbackInput();
@@ -532,6 +604,10 @@ async function main() {
532
604
  setRawMode(true);
533
605
  rawEnabled = process.stdin.isTTY;
534
606
  sendResize(ws, options);
607
+ if (bufferedInput.length) {
608
+ ws.send(MSG_INPUT + bufferedInput.toString("base64"));
609
+ bufferedInput = Buffer.alloc(0);
610
+ }
535
611
  return;
536
612
  }
537
613
 
@@ -558,6 +634,7 @@ pid:
558
634
  return;
559
635
  }
560
636
  closed = true;
637
+ clearTimeout(probeFlushTimer);
561
638
  if (pingTimer) {
562
639
  clearInterval(pingTimer);
563
640
  pingTimer = null;
@@ -565,8 +642,13 @@ pid:
565
642
  setWritable(false);
566
643
  stopFallbackInput();
567
644
  stopViewerInput();
645
+ if (process.stdout.isTTY) {
646
+ try {
647
+ writeTerminal(modeTransition(localModes.snapshot(), originalModes, { normalizeMouse: true, kittyRestore: captured.probe.kittyRestore }));
648
+ } catch {}
649
+ }
568
650
  if (rawEnabled) {
569
- setRawMode(false);
651
+ setRawMode(originalRawMode);
570
652
  rawEnabled = false;
571
653
  }
572
654
  process.stdin.pause();
@@ -611,7 +693,7 @@ pid:
611
693
  break;
612
694
  }
613
695
  pendingKittyCursorMotion = "";
614
- process.stdout.write(output);
696
+ writeTerminal(output);
615
697
  if (fallbackProbe && accessRole === "") {
616
698
  fallbackOutputTail = normalizeEcho(
617
699
  `${fallbackOutputTail}${output.toString("utf8")}`
@@ -641,6 +723,14 @@ pid:
641
723
  if (encoded) process.stdout.write(encoded);
642
724
  } catch {}
643
725
  break;
726
+ case MSG_RESTORE_MODES:
727
+ // Only local terminal state; the PTY keeps its modes for the next resume.
728
+ if (process.stdout.isTTY) {
729
+ try {
730
+ writeTerminal(modeTransition(localModes.snapshot(), validateSnapshot(JSON.parse(payload)), { normalizeMouse: true, kittyRestore: captured.probe.kittyRestore }));
731
+ } catch {}
732
+ }
733
+ break;
644
734
  case MSG_SET_RECONNECT: {
645
735
  try {
646
736
  const reconnect = JSON.parse(payload);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ // Copied verbatim from jsmdcui/src/platform/clipboard.js (MIT).
3
+ // Keep the function below unchanged; inject its existing command helpers.
4
+ module.exports = function createPowerShellClipboard(shell, runSync, outputOrThrow) {
5
+ function powershellClipboard(shell) {
6
+ return {
7
+ name: "powershell",
8
+ // Get-Clipboard -Raw appends \r\n to stdout; strip exactly one trailing line ending.
9
+ read: () => outputOrThrow(runSync([shell, "-NoProfile", "-Command", "Get-Clipboard -Raw"], {})).replace(/\r?\n$/, ""),
10
+ write: (text) => {
11
+ text=(text+'').replaceAll("'","''") ;
12
+ return runSync([shell, "-NoProfile", "-Command", `Set-Clipboard '${text}'`], { stdout: "ignore" }).ok ;
13
+ },
14
+ };
15
+ }
16
+
17
+ return powershellClipboard(shell);
18
+ };
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ const createPowerShellClipboard = require('./clipboard-powershell.js');
3
+ // Self-contained so the worker remains valid after bundling/minification.
4
+ function clipboardRuntime(createPowerShellClipboard) {
5
+ const {spawnSync} = require('node:child_process');
6
+ const fs = require('node:fs');
7
+ const path = require('node:path');
8
+ const MAX_OSC52 = 1024 * 1024;
9
+ function which(name) {
10
+ if (typeof Bun !== 'undefined') return Bun.which(name);
11
+ const names=process.platform==='win32' && !path.extname(name)
12
+ ? [name,...(process.env.PATHEXT || '.EXE;.CMD;.BAT').split(';').map(ext=>name+ext)] : [name];
13
+ for (const dir of (process.env.PATH || '').split(path.delimiter)) {
14
+ for(const candidate of names) {
15
+ const file=path.join(dir,candidate);
16
+ try {fs.accessSync(file,fs.constants.X_OK);if(fs.statSync(file).isFile())return file;} catch {}
17
+ }
18
+ }
19
+ return null;
20
+ }
21
+ function runSync(command, options={}) {
22
+ try {
23
+ const r=spawnSync(command[0],command.slice(1),{
24
+ input:options.stdin, encoding:'utf8', timeout:options.timeout ?? 2000,
25
+ maxBuffer:MAX_OSC52, stdio:['pipe',options.stdout ?? 'pipe','pipe'],windowsHide:true,
26
+ });
27
+ return {ok:!r.error && r.status===0,stdout:r.stdout || '',stderr:r.stderr || String(r.error || '')};
28
+ } catch(error) {return {ok:false,stdout:'',stderr:String(error)};}
29
+ }
30
+ function outputOrThrow(result) {
31
+ if (!result.ok) throw new Error(result.stderr || result.stdout || 'clipboard command failed');
32
+ return result.stdout;
33
+ }
34
+ function createClipboard({find=which,run=runSync,platform=process.platform}={}) {
35
+ const internal=new Map();
36
+ function candidates(register,write,text) {
37
+ const result=[];
38
+ const command=(name,args)=>{const executable=find(name);if(executable)result.push(()=>run([executable,...args],write?{stdin:text,stdout:'ignore'}:{}));};
39
+ // Always first, including Windows and Android. Failure does not pin a backend.
40
+ command('xclip',['-selection',register,...(write?[]:['-o'])]);
41
+ if(register==='clipboard') {
42
+ command(write?'termux-clipboard-set':'termux-clipboard-get',[]);
43
+ }
44
+ if(register!=='secondary')command(write?'wl-copy':'wl-paste',[...(write?[]:['--no-newline']),...(register==='primary'?['--primary']:[])]);
45
+ command('xsel',[`--${register}`,write?'--input':'--output']);
46
+ if(register==='clipboard') {
47
+ if(platform==='darwin')command(write?'pbcopy':'pbpaste',[]);
48
+ if(platform==='win32')for(const name of ['pwsh.exe','powershell.exe','pwsh','powershell']){
49
+ const shell=find(name);if(!shell)continue;
50
+ const backend=createPowerShellClipboard(shell,run,outputOrThrow);
51
+ result.push(()=>write?{ok:backend.write(text)}:{ok:true,stdout:backend.read()});
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ return {
57
+ read(register='clipboard') {
58
+ if(/^cut[0-7]$/.test(register))return internal.get(register);
59
+ for(const attempt of candidates(register,false))try {
60
+ const r=attempt();if(r.ok){internal.set(register,r.stdout);return r.stdout;}
61
+ }catch{}
62
+ return internal.get(register);
63
+ },
64
+ write(text,register='clipboard') {
65
+ internal.set(register,text);
66
+ if(/^cut[0-7]$/.test(register))return;
67
+ for(const attempt of candidates(register,true,text))try {if(attempt().ok)return;}catch{}
68
+ },
69
+ };
70
+ }
71
+ function parseOSC52(data) {
72
+ if(typeof data!=='string'||data.length>MAX_OSC52)return null;
73
+ const i=data.indexOf(';');if(i<0)return null;
74
+ const selection=data.slice(0,i),payload=data.slice(i+1);
75
+ if(!/^[cpqs0-7]*$/.test(selection)||selection.length>16)return null;
76
+ const registers=[...new Set([...(selection || 's0')].map(c=>({c:'clipboard',p:'primary',q:'secondary',s:'clipboard'}[c]||`cut${c}`)))];
77
+ if(payload==='?')return {selection,registers,read:true};
78
+ // OSC 52 specifies clearing the selection for non-base64 data.
79
+ const valid=/^[A-Za-z0-9+/]*={0,2}$/.test(payload) && payload.replace(/=+$/,'').length % 4 !== 1;
80
+ return {selection,registers,read:false,text:valid?Buffer.from(payload,'base64').toString('utf8'):''};
81
+ }
82
+ function executeOSC52(clipboard,request) {
83
+ if(request.read) {
84
+ let text;
85
+ for(const register of request.registers){text=clipboard.read(register);if(text!==undefined)break;}
86
+ const data=Buffer.from(text ?? '', 'utf8');
87
+ if(data.length>MAX_OSC52)return null;
88
+ return `\x1b]52;${request.selection};${data.toString('base64')}\x1b\\`;
89
+ }
90
+ for(const register of request.registers)clipboard.write(request.text,register);
91
+ return null;
92
+ }
93
+ return {createClipboard,parseOSC52,executeOSC52,MAX_OSC52};
94
+ }
95
+ const {createClipboard,parseOSC52,executeOSC52,MAX_OSC52}=clipboardRuntime(createPowerShellClipboard);
96
+ module.exports={createClipboard,parseOSC52,executeOSC52,MAX_OSC52};
97
+
98
+ // Run the synchronous jsmdcui-compatible backends off the server event loop.
99
+ // Inline the helpers so compiled single-file builds need no external worker file.
100
+ class ClipboardHost {
101
+ constructor(){this.pending=new Map();this.nextId=0;this.worker=null;}
102
+ execute(request){
103
+ if(this.pending.size>=32)return Promise.reject(Error('clipboard queue full'));
104
+ if(!this.worker){
105
+ const {Worker}=require('node:worker_threads');
106
+ const source=`const {parentPort}=require('node:worker_threads');
107
+ const runtime=(${clipboardRuntime.toString()})(${createPowerShellClipboard.toString()});
108
+ const clipboard=runtime.createClipboard();parentPort.on('message',({id,request})=>{
109
+ try{parentPort.postMessage({id,result:runtime.executeOSC52(clipboard,request)});}
110
+ catch(error){parentPort.postMessage({id,error:String(error)});}
111
+ });`;
112
+ const worker=this.worker=new Worker(source,{eval:true});
113
+ const failed=error=>{
114
+ if(this.worker!==worker)return;
115
+ this.worker=null;
116
+ for(const job of this.pending.values())job.reject(error);
117
+ this.pending.clear();worker.terminate();
118
+ };
119
+ worker.on('error',failed);
120
+ worker.on('exit',()=>failed(Error('clipboard worker exited')));
121
+ worker.on('message',({id,result,error})=>{
122
+ const job=this.pending.get(id);if(!job)return;
123
+ this.pending.delete(id);error?job.reject(Error(error)):job.resolve(result);
124
+ if(!this.pending.size)worker.unref();
125
+ });
126
+ worker.unref();
127
+ }
128
+ return new Promise((resolve,reject)=>{
129
+ const id=++this.nextId;this.pending.set(id,{resolve,reject});this.worker.ref();
130
+ this.worker.postMessage({id,request});
131
+ });
132
+ }
133
+ async close(){
134
+ const worker=this.worker;this.worker=null;
135
+ for(const job of this.pending.values())job.reject(Error('clipboard closed'));
136
+ this.pending.clear();if(worker)await worker.terminate();
137
+ }
138
+ }
139
+ // Authorization is checked both before host access and before returning data.
140
+ function handleOSC52(session,ws,payload,host){
141
+ if(!session.isWriter(ws))return;
142
+ const request=parseOSC52(payload);if(!request)return;
143
+ host.execute(request).then(reply=>{
144
+ if(reply && !session.closed && session.isWriter(ws))session.backend.write(Buffer.from(reply));
145
+ }).catch(()=>{});
146
+ }
147
+ module.exports.ClipboardHost=ClipboardHost;
148
+ module.exports.handleOSC52=handleOSC52;
@@ -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