buninu-linux 0.14.57 → 0.16.59

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 (299) hide show
  1. package/LICENSES/linux-firmware/LICENCE.nvidia +131 -0
  2. package/LICENSES/linux-firmware/LICENSE.amdgpu +51 -0
  3. package/LICENSES/linux-firmware/LICENSE.amdisp +31 -0
  4. package/LICENSES/linux-firmware/LICENSE.i915 +39 -0
  5. package/LICENSES/linux-firmware/LICENSE.radeon +51 -0
  6. package/LICENSES/linux-firmware/LICENSE.xe +39 -0
  7. package/LICENSES/linux-firmware/WHENCE +10280 -0
  8. package/NOTICE.md +64 -46
  9. package/README.md +378 -87
  10. package/README.zh.md +2041 -0
  11. package/build-image.sh +10 -1
  12. package/build-uki.sh +3 -1
  13. package/fetch-alpine.sh +14 -0
  14. package/graphics.md +31 -0
  15. package/index.js +28 -11
  16. package/initramfs/bin/buninu-browser +4 -1
  17. package/initramfs/bin/buninu-land +1 -1
  18. package/initramfs/bin/bunterm +3 -3
  19. package/initramfs/bin/chroot +13 -0
  20. package/initramfs/bin/chvt +11 -0
  21. package/initramfs/bin/mount +4 -0
  22. package/initramfs/bin/umount +6 -0
  23. package/initramfs/bin/xdg-open +33 -0
  24. package/initramfs/buninu/CHANGELOG.md +66 -0
  25. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +4 -0
  26. package/initramfs/buninu/apps/bunmsh/package.json +1 -1
  27. package/initramfs/buninu/apps/bunmsh/src/main.js +1 -1
  28. package/initramfs/buninu/apps/jsgotty/LICENSES/MIT-jsmdcui.txt +22 -0
  29. package/initramfs/buninu/apps/jsgotty/NOTICE +5 -0
  30. package/initramfs/buninu/apps/jsgotty/README.en.md +61 -0
  31. package/initramfs/buninu/apps/jsgotty/README.md +61 -0
  32. package/initramfs/buninu/apps/jsgotty/client-modes.mjs +117 -0
  33. package/initramfs/buninu/apps/jsgotty/client.mjs +100 -10
  34. package/initramfs/buninu/apps/jsgotty/clipboard-powershell.js +18 -0
  35. package/initramfs/buninu/apps/jsgotty/clipboard.js +148 -0
  36. package/initramfs/buninu/apps/jsgotty/gotty.js +13 -0
  37. package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
  38. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  39. package/initramfs/buninu/apps/jsgotty/static/css/index.css +14 -1
  40. package/initramfs/buninu/apps/jsgotty/static/index.html +1 -0
  41. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +187 -23
  42. package/initramfs/buninu/apps/jsgotty/static/js/kitty-keyboard.js +193 -0
  43. package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +115 -7
  44. package/initramfs/buninu/apps/jsgotty/terminal-modes.js +332 -0
  45. package/initramfs/buninu/apps/jsgotty/tests/android-alt.test.js +115 -0
  46. package/initramfs/buninu/apps/jsgotty/tests/arrow-modifiers.test.js +80 -0
  47. package/initramfs/buninu/apps/jsgotty/tests/client-exit.test.mjs +45 -0
  48. package/initramfs/buninu/apps/jsgotty/tests/client-modes.test.mjs +105 -0
  49. package/initramfs/buninu/apps/jsgotty/tests/clipboard-browser.test.js +81 -0
  50. package/initramfs/buninu/apps/jsgotty/tests/clipboard.test.js +75 -0
  51. package/initramfs/buninu/apps/jsgotty/tests/kitty-keyboard-browser.test.js +53 -0
  52. package/initramfs/buninu/apps/jsgotty/tests/kitty-keyboard.test.js +102 -0
  53. package/initramfs/buninu/apps/jsgotty/tests/manual/README.md +20 -0
  54. package/initramfs/buninu/apps/jsgotty/tests/manual/keyboard-collector.cjs +55 -0
  55. package/initramfs/buninu/apps/jsgotty/tests/manual/keyboard-debug.js +74 -0
  56. package/initramfs/buninu/apps/jsgotty/tests/manual/kitty-keyboard.js +80 -0
  57. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-collector.cjs +55 -0
  58. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-debug.js +39 -0
  59. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.js +33 -0
  60. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.mjs +22 -0
  61. package/initramfs/buninu/apps/jsgotty/tests/nested-cli-pty.test.mjs +116 -0
  62. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes-browser.test.js +144 -0
  63. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.md +44 -0
  64. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.test.js +166 -0
  65. package/initramfs/buninu/apps/jsgotty/tests/touch-drag-browser.test.js +106 -0
  66. package/initramfs/buninu/apps/jsgotty/tests/wheel-browser.test.js +60 -0
  67. package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +44 -0
  68. package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
  69. package/initramfs/buninu/apps/jsmdcui/src/highlight/go-regexp.js +83 -0
  70. package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +49 -62
  71. package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +8 -5
  72. package/initramfs/buninu/apps/jsmdcui/tests/go-regexp.test.js +98 -0
  73. package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +21 -16
  74. package/initramfs/buninu/apps/jsmdcui/tests/highlight-regions.test.js +73 -0
  75. package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +32 -9
  76. package/initramfs/buninu/apps/jsmdcui/tests/markdown-behavior.mjs +9 -0
  77. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/NORMAL-HTML.md +46 -0
  78. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/README.md +85 -0
  79. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/REPORT.md +328 -0
  80. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/compare.mjs +75 -0
  81. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/go.mod +5 -0
  82. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html-report.json +619 -0
  83. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html.html +9 -0
  84. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/oracle.go +49 -0
  85. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/report.json +2174 -0
  86. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/PowerShell.txt +39 -0
  87. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ada.txt +25 -0
  88. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/apacheconf.txt +49 -0
  89. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/arduino.txt +38 -0
  90. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asciidoc.txt +33 -0
  91. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asm.txt +77 -0
  92. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ats.txt +48 -0
  93. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/awk.txt +28 -0
  94. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/b.txt +27 -0
  95. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/bat.txt +39 -0
  96. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/c.txt +34 -0
  97. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/caddyfile.txt +14 -0
  98. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cake.txt +7 -0
  99. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/clojure.txt +18 -0
  100. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cmake.txt +20 -0
  101. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/coffeescript.txt +32 -0
  102. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/colortest.txt +19 -0
  103. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/conky.txt +12 -0
  104. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cpp.txt +46 -0
  105. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crontab.txt +16 -0
  106. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crystal.txt +28 -0
  107. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csharp.txt +28 -0
  108. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/css.txt +19 -0
  109. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csx.txt +7 -0
  110. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cuda.txt +37 -0
  111. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cython.txt +22 -0
  112. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/d.txt +52 -0
  113. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dart.txt +27 -0
  114. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/default.txt +8 -0
  115. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dockerfile.txt +10 -0
  116. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dot.txt +15 -0
  117. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elixir.txt +23 -0
  118. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elm.txt +20 -0
  119. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erb.txt +32 -0
  120. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erlang.txt +23 -0
  121. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fish.txt +20 -0
  122. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/forth.txt +17 -0
  123. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fortran.txt +40 -0
  124. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/freebsd-kernel.txt +10 -0
  125. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fsharp.txt +27 -0
  126. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gdscript.txt +26 -0
  127. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gemini.txt +11 -0
  128. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-ebuild.txt +28 -0
  129. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-etc-portage.txt +14 -0
  130. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-commit.txt +19 -0
  131. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-config.txt +11 -0
  132. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-rebase-todo.txt +10 -0
  133. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gleam.txt +44 -0
  134. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/glsl.txt +18 -0
  135. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gnuplot.txt +11 -0
  136. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/go.txt +33 -0
  137. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/godoc.txt +9 -0
  138. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/golo.txt +50 -0
  139. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gomod.txt +16 -0
  140. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/graphql.txt +20 -0
  141. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groff.txt +18 -0
  142. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groovy.txt +42 -0
  143. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haml.txt +16 -0
  144. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hare.txt +31 -0
  145. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haskell.txt +26 -0
  146. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hc.txt +28 -0
  147. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html.txt +26 -0
  148. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html4.txt +20 -0
  149. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html5.txt +20 -0
  150. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ini.txt +16 -0
  151. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/inputrc.txt +14 -0
  152. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/java.txt +18 -0
  153. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/javascript.txt +41 -0
  154. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jinja2.txt +13 -0
  155. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/json.txt +21 -0
  156. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jsonnet.txt +28 -0
  157. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/julia.txt +24 -0
  158. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/justfile.txt +26 -0
  159. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/keymap.txt +15 -0
  160. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kickstart.txt +16 -0
  161. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kotlin.txt +30 -0
  162. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kvlang.txt +37 -0
  163. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ledger.txt +13 -0
  164. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lfe.txt +17 -0
  165. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lilypond.txt +15 -0
  166. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lisp.txt +17 -0
  167. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/log.txt +45 -0
  168. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lua.txt +48 -0
  169. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mail.txt +17 -0
  170. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/makefile.txt +25 -0
  171. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/man.txt +12 -0
  172. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/markdown.txt +21 -0
  173. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mc.txt +11 -0
  174. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/meson.txt +29 -0
  175. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/micro.txt +25 -0
  176. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mpdconf.txt +13 -0
  177. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/msbuild.txt +7 -0
  178. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nanorc.txt +16 -0
  179. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nftables.txt +22 -0
  180. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nginx.txt +17 -0
  181. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nim.txt +23 -0
  182. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nix.txt +15 -0
  183. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nu.txt +92 -0
  184. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/objc.txt +36 -0
  185. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ocaml.txt +25 -0
  186. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/octave.txt +35 -0
  187. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/odin.txt +35 -0
  188. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pascal.txt +22 -0
  189. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/patch.txt +13 -0
  190. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/peg.txt +16 -0
  191. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/perl.txt +27 -0
  192. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/php.txt +44 -0
  193. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pkg-config.txt +12 -0
  194. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/po.txt +12 -0
  195. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pony.txt +29 -0
  196. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pov.txt +17 -0
  197. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-action.txt +14 -0
  198. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-config.txt +10 -0
  199. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-filter.txt +12 -0
  200. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/proto.txt +22 -0
  201. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/prql.txt +42 -0
  202. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/puppet.txt +22 -0
  203. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python2.txt +24 -0
  204. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python3.txt +28 -0
  205. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/r.txt +16 -0
  206. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/raku.txt +19 -0
  207. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/reST.txt +18 -0
  208. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/renpy.txt +11 -0
  209. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rpmspec.txt +31 -0
  210. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ruby.txt +34 -0
  211. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rust.txt +27 -0
  212. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sage.txt +24 -0
  213. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scad.txt +23 -0
  214. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scala.txt +16 -0
  215. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sed.txt +12 -0
  216. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sh.txt +20 -0
  217. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sls.txt +15 -0
  218. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/smalltalk.txt +28 -0
  219. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/solidity.txt +26 -0
  220. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sql.txt +35 -0
  221. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/stata.txt +32 -0
  222. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/svelte.txt +10 -0
  223. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/swift.txt +53 -0
  224. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/systemd.txt +15 -0
  225. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tcl.txt +17 -0
  226. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/terraform.txt +28 -0
  227. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tex.txt +15 -0
  228. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/toml.txt +31 -0
  229. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/twig.txt +29 -0
  230. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/typescript.txt +32 -0
  231. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/v.txt +47 -0
  232. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vala.txt +22 -0
  233. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/verilog.txt +37 -0
  234. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vhdl.txt +37 -0
  235. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vi.txt +17 -0
  236. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vue.txt +29 -0
  237. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xml.txt +14 -0
  238. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xresources.txt +14 -0
  239. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yaml.txt +19 -0
  240. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yum.txt +12 -0
  241. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zig.txt +24 -0
  242. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zscript.txt +31 -0
  243. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zsh.txt +21 -0
  244. package/initramfs/buninu/package.json +1 -1
  245. package/initramfs/init.js +19 -3
  246. package/initramfs/lib/bunterm/clipboard.js +14 -0
  247. package/initramfs/lib/bunterm/commands.js +37 -0
  248. package/initramfs/lib/bunterm/fps.js +38 -0
  249. package/initramfs/lib/bunterm/images.js +14 -6
  250. package/initramfs/lib/bunterm/input.js +58 -15
  251. package/initramfs/lib/bunterm/keyboard.js +62 -0
  252. package/initramfs/lib/bunterm/keycodes.js +104 -0
  253. package/initramfs/lib/bunterm/kitty-keyboard.cjs +195 -0
  254. package/initramfs/lib/bunterm/raw-protocol.js +111 -0
  255. package/initramfs/lib/bunterm/renderer.js +116 -28
  256. package/initramfs/lib/bunterm/selection.js +60 -0
  257. package/initramfs/lib/bunterm/terminal.js +157 -12
  258. package/initramfs/lib/bunterm/vt-menu.js +181 -0
  259. package/initramfs/lib/bunterm/vt.js +27 -1
  260. package/initramfs/lib/canvas.js +99 -0
  261. package/initramfs/lib/chvt.js +31 -0
  262. package/initramfs/lib/fbdev.js +32 -8
  263. package/initramfs/lib/gpu.js +77 -0
  264. package/initramfs/lib/mapped-frame.js +28 -0
  265. package/initramfs/lib/raw-image.js +71 -0
  266. package/initramfs/lib/xdg-open.js +42 -0
  267. package/initramfs/usr/share/doc/buninu-linux/bunterm.md +85 -11
  268. package/initramfs/usr/share/doc/buninu-linux/chvt.md +34 -0
  269. package/initramfs/usr/share/doc/buninu-linux/xdg-open.md +43 -0
  270. package/package.json +1 -1
  271. package/scripts/arch.js +7 -5
  272. package/scripts/arch.sh +5 -5
  273. package/scripts/build.js +16 -3
  274. package/scripts/fetch-gpu.js +105 -0
  275. package/scripts/fetch.js +11 -1
  276. package/scripts/gpu-firmware.json +22 -0
  277. package/scripts/modules.js +4 -1
  278. package/scripts/npm-packages.md +18 -17
  279. package/test/bunterm-fps.test.js +17 -0
  280. package/test/bunterm-image-bytes.test.js +15 -0
  281. package/test/bunterm-keyboard.test.js +116 -0
  282. package/test/bunterm-mapped-lifetime.test.js +147 -0
  283. package/test/bunterm-next-vt.test.js +9 -0
  284. package/test/bunterm-raw-protocol.test.js +39 -0
  285. package/test/bunterm-renderer.test.js +167 -0
  286. package/test/bunterm-selection.test.js +38 -0
  287. package/test/bunterm-vt-menu.test.js +178 -0
  288. package/test/chroot-bind.test.js +40 -2
  289. package/test/chvt.test.js +46 -0
  290. package/test/fbdev-sync.test.js +42 -0
  291. package/test/fetch-npm.test.js +2 -2
  292. package/test/gpu.test.js +155 -0
  293. package/test/image-size.test.js +17 -0
  294. package/test/manual-bunterm-image-paths.js +31 -0
  295. package/test/mount-libc-lifetime.test.js +50 -0
  296. package/test/no-gpu.test.js +43 -0
  297. package/test/readme-cli.test.js +29 -0
  298. package/test/stage-npm.test.js +11 -4
  299. package/test/xdg-open.test.js +55 -0
@@ -3,6 +3,11 @@
3
3
  [English README](README.en.md)
4
4
 
5
5
  ## News
6
+
7
+ ### 2026/10/21
8
+
9
+ - 新增 [Kitty keyboard](#kitty-keyboard) 支援,可區分 Super 等修飾鍵組合,並回報按下、重複與放開事件。
10
+
6
11
  ### 2026/08/16
7
12
  - 修正 WebGL 模式下斷線畫面的 `Reconnect` 按鈕無法點擊。
8
13
  - `Reconnect` 介面現在會正確位於 WebGL canvas 上方並接收滑鼠與觸控事件;右鍵/長按輸入 reconnect token 或 PID 也不會被 Kitty 圖片事件攔截。
@@ -238,6 +243,41 @@ bun gotty.js --client -k <token|pid> [target]
238
243
  - 斷線時會提供 `Reconnect` 按鈕,可手動立即重試。
239
244
  - 該按鈕右鍵/長按可輸入自訂 reconnect token 或 PID。
240
245
 
246
+ ## Kitty keyboard
247
+
248
+ 網頁終端現在支援 [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) 的快捷鍵相關功能,與 Kitty 圖片協定分開。預設仍是原本的鍵盤編碼;由終端內的程式選擇開啟:
249
+
250
+ ```sh
251
+ printf '\033[>11u' # 1:消除歧義 + 2:重複/放開事件 + 8:回報所有按鍵
252
+ printf '\033[<u' # 程式退出時還原上一層模式
253
+ ```
254
+
255
+ 程式必須能解析新編碼,並在 finally/退出處理中還原模式,不建議直接在一般 shell 長期開啟。支援 `CSI ? u` 查詢、`CSI = flags ; mode u` 設定/加上/移除旗標、`CSI > flags u` push 與 `CSI < count u` pop。主畫面與 alternate screen 各有獨立堆疊(上限 32 層);重連、完整重設或 soft reset 會清除狀態。只需要無歧義快捷鍵時可用旗標 `1`;需要獨立修飾鍵及所有按鍵的 down/repeat/up 時使用 `11`。
256
+
257
+ 網頁的 Meta 對應 Kitty 的 **Super(bit 8)**,不是協定中的另一個 Meta(bit 32)。例如 Super+X 是 `CSI 120;9u`,Ctrl+Shift+Super+X 是 `CSI 120;14u`;啟用事件回報後,放開會帶 `:3`。
258
+
259
+ 目前支援旗標 **1、2、8**;尚未實作替代鍵位(4)與附帶文字(16),查詢只回報實際啟用的旗標。IME 組字/文字提交與貼上保留既有路徑,不保證能回報實體按鍵或放開事件。字母、數字、標點、方向鍵、Tab、Enter、功能鍵、數字鍵盤與有標準 Kitty 編碼的媒體鍵可區分修飾組合;系統/瀏覽器先攔截的快捷鍵不會到達網頁,亮度、麥克風靜音等沒有標準 Kitty 鍵碼的鍵也不會擅自編成私有序列。
260
+
261
+ 這完成的是 **jsgotty → PTY** 的鍵盤編碼。要操作 Hyprland/Omarchy,Casty 或其他接收程式仍須啟用、解析 Kitty keyboard 並轉送 Super 與放開事件;此項改動不代表整條遠端桌面輸入鏈已完成,也不改變滑鼠協定。
262
+
263
+ 在 jsgotty 終端內手動確認(60 秒後自動退出,或按未修飾的 Esc/q/Ctrl+C):
264
+
265
+ ```sh
266
+ bun tests/manual/kitty-keyboard.js
267
+ ```
268
+
269
+ 測試會印出協商結果、原始序列、修飾鍵及 press/repeat/release;結束時還原鍵盤模式與 raw mode。更新後請重新載入 jsgotty 網頁。可用既有 Chromium 執行真正 xterm 前端測試,不會自動安裝瀏覽器:
270
+
271
+ ```sh
272
+ CHROMIUM_HEADLESS=/path/to/chromium-headless-shell node --test tests/kitty-keyboard-browser.test.js
273
+ ```
274
+
275
+ ## Android 鍵盤
276
+
277
+ Android WebView 若有 `Meta` down/up 卻漏掉 `metaKey`,jsgotty 會在自己的輸入處理中追蹤左右 Meta 並還原 Super。失焦或頁面隱藏時清除按住狀態;已送出的按鍵在事件回報模式下會補送放開。
278
+
279
+ Android 終端的 Alt+字母、數字、標點及空白會依實體按鍵位置(美式配置,支援 Shift)送出終端 Alt 序列,不進入重音死鍵流程。因此 Alt-E/I/N/U 等組合不再用來輸入 é、ê、ñ、ü;仍可使用輸入法或貼上。若 WebView 省略 `code`,會回退使用可辨識的 `keyCode`;若死鍵只送出 `keyup`,則在放開時補送一次,避免與已處理的 `keydown` 重複。正在組字、Ctrl、Meta、AltGr,以及無法辨識按鍵的事件維持原本處理。此規則只作用於 jsgotty 終端,不修改 Android 的系統鍵盤配置。
280
+
241
281
  ## Tested platforms 已測試作業系統
242
282
  - Windows x64: Windows 11
243
283
  - Linux x64: CachyOS
@@ -294,3 +334,24 @@ bun sz.js file.txt
294
334
  - 以 Bun 為主要執行方式。
295
335
  - Linux / macOS 如果不是用 Bun 執行,PTY 會無法使用。
296
336
  - 目前未實作 Go 版的 config 檔載入
337
+
338
+ ## OSC 52 剪貼板
339
+
340
+ 瀏覽器版 jsgotty 支援 OSC 52 寫入與 `?` 查詢。操作的是 **server 所在主機**的剪貼板,不是瀏覽器裝置的剪貼板。只有 session 的 writer 可以執行;viewer 不會重複操作。查詢結果以 OSC 52 回覆直接送回原本的 PTY。
341
+
342
+ 每次操作都先嘗試 `xclip`(包括 Windows/Android),不存在或執行失敗才依序嘗試適用的 `termux-clipboard-*`、`wl-copy`/`wl-paste`、`xsel`、macOS 的 `pbcopy`/`pbpaste`、Windows 的 PowerShell。PowerShell 函式原封不動取自 jsmdcui。所有工具都失敗時使用 server 記憶體快取;成功讀到空字串就是空剪貼板,不會被舊快取取代。沒有輪詢,外部工具在 worker 中執行。
343
+
344
+ 支援 `c`(clipboard)、`p`(primary)、`q`(secondary);`s` 對應 clipboard,`0`~`7` 為記憶體暫存。空選擇使用 `s0`。不提供 primary/secondary 的工具不會拿 clipboard 冒充。單次 OSC 52 請求上限 1 MiB。
345
+
346
+ ```sh
347
+ # 寫入 server 剪貼板
348
+ printf '\033]52;c;%s\007' "$(printf 'Hello' | base64 | tr -d '\n')"
349
+ ```
350
+
351
+ 程式可送出 `ESC ] 52 ; c ; ? ST` 讀取,並從 stdin 接收 base64 編碼的 OSC 52 回覆。
352
+
353
+ `jsgotty --client` **不攔截 OSC 52**,讀寫控制碼都原樣轉送,交給最外層真正的終端模擬器決定。
354
+
355
+ ### 觸控拖曳與右鍵
356
+
357
+ 在程式啟用滑鼠回報時,快速點兩下並按住第二下可拖曳左鍵,放開即釋放;單指滑動仍是捲動。既有圖片長按選單的 `Remove image` 下方新增 `Right click`,會在叫出選單的原始位置送出右鍵點擊。雙指縮放與原本的移除圖片入口不變。若要停用雙擊拖曳,可在網頁 JavaScript 設定 `window.touchDoubleTapDrag = false`。
@@ -0,0 +1,117 @@
1
+ // Read the terminal before attaching. No reset and no guessed default modes.
2
+ import { randomBytes } from 'node:crypto';
3
+ import modes from './terminal-modes.js';
4
+ const {TerminalModeTracker, validateSnapshot, TRACKED_DEC_FLAGS, MOUSE_PROTOCOLS, MOUSE_ENCODINGS} = modes;
5
+ const ESC = '\x1b';
6
+ export class ModeProbe {
7
+ constructor(nonce = randomBytes(16).toString('hex')) {
8
+ this.nonce = nonce;
9
+ this.pending = Buffer.alloc(0);
10
+ this.complete = false;
11
+ this.kittyRestore = false;
12
+ this.state = new TerminalModeTracker().snapshot();
13
+ for (const key of Object.keys(this.state.dec)) this.state.dec[key] = null;
14
+ for (const key of ['alternate','cursorHidden','mouse','encoding']) this.state[key] = null;
15
+ this.state.kitty.normal.flags = this.state.kitty.alternate.flags = null;
16
+ this.state.mouseModeBits = Object.fromEntries(Object.keys(MOUSE_PROTOCOLS).map(n=>[n,null]));
17
+ this.state.mouseEncodingBits = Object.fromEntries(Object.keys(MOUSE_ENCODINGS).map(n=>[n,null]));
18
+ this.replies = new Map();
19
+ this.outstanding = new Set();
20
+ this.kittyOutstanding = false;
21
+ this.privatePrefix = `${ESC}]777;jsgotty-modes;${nonce};`;
22
+ }
23
+ privateQuery() { return `${ESC}]777;jsgotty-modes;${this.nonce}\x07`; }
24
+ standardQuery() {
25
+ this.outstanding = new Set([...Object.keys(TRACKED_DEC_FLAGS),25,1049,...Object.keys(MOUSE_PROTOCOLS),...Object.keys(MOUSE_ENCODINGS)].map(Number));
26
+ this.kittyOutstanding = true;
27
+ this.replyDeadline = Date.now() + 1500;
28
+ return [...this.outstanding]
29
+ .map(n => `${ESC}[?${n}$p`).join('') + `${ESC}[?u`;
30
+ }
31
+ reply(mode, value) {
32
+ if (this.complete) return;
33
+ const yes = value === 1 || value === 3;
34
+ // An unsupported mode cannot be active, but unlike silence it is a reply.
35
+ // Still aggregate below when 0 is the last answer in a mouse-mode group.
36
+ this.replies.set(mode, yes);
37
+ if (MOUSE_PROTOCOLS[mode]) this.state.mouseModeBits[mode] = yes;
38
+ if (MOUSE_ENCODINGS[mode]) this.state.mouseEncodingBits[mode] = yes;
39
+ if (value !== 0) {
40
+ if (TRACKED_DEC_FLAGS[mode]) this.state.dec[TRACKED_DEC_FLAGS[mode]] = yes;
41
+ else if (mode === 25) this.state.cursorHidden = !yes;
42
+ else if (mode === 1049) this.state.alternate = yes;
43
+ }
44
+ for (const [name, table, fallback, priority] of [
45
+ ['mouse',MOUSE_PROTOCOLS,'NONE',[1003,1002,1000,9]],
46
+ ['encoding',MOUSE_ENCODINGS,'DEFAULT',[1016,1006]],
47
+ ]) {
48
+ const active = priority.find(key => this.replies.get(key) === true);
49
+ if (active) this.state[name] = table[active];
50
+ else if (Object.keys(table).every(key => this.replies.has(+key))) this.state[name] = fallback;
51
+ }
52
+ }
53
+ consume(chunk) {
54
+ if ((this.complete && !this.outstanding.size && !this.kittyOutstanding) ||
55
+ (this.replyDeadline && Date.now() > this.replyDeadline)) {
56
+ this.outstanding.clear(); this.kittyOutstanding = false;
57
+ return Buffer.concat([this.flush(), Buffer.from(chunk)]);
58
+ }
59
+ this.pending = Buffer.concat([this.pending, Buffer.from(chunk)]);
60
+ const out = [];
61
+ while (this.pending.length) {
62
+ const text = this.pending.toString('latin1');
63
+ if (text.startsWith(this.privatePrefix)) {
64
+ const end = text.indexOf('\x07', this.privatePrefix.length);
65
+ if (end < 0 && text.length < 16384) break;
66
+ if (end >= 0) {
67
+ try {
68
+ const reply = JSON.parse(Buffer.from(text.slice(this.privatePrefix.length,end),'base64').toString());
69
+ this.state = validateSnapshot(reply);
70
+ this.kittyRestore = reply.kittyRestore === 1;
71
+ this.complete = true;
72
+ } catch {}
73
+ this.pending = this.pending.subarray(end+1); continue;
74
+ }
75
+ }
76
+ const dec = /^\x1b\[\?(\d+);([0-4])\$y/.exec(text);
77
+ const kitty = /^\x1b\[\?(\d+)u/.exec(text);
78
+ if ((dec && this.outstanding.delete(+dec[1])) || (kitty && this.kittyOutstanding)) {
79
+ if (dec) this.reply(+dec[1],+dec[2]);
80
+ else {
81
+ this.kittyOutstanding = false;
82
+ if (!this.complete && this.state.alternate != null)
83
+ this.state.kitty[this.state.alternate?'alternate':'normal'].flags = +kitty[1] & 31;
84
+ }
85
+ this.pending = this.pending.subarray((dec||kitty)[0].length); continue;
86
+ }
87
+ // Hold only possible query replies; normal keystrokes and mouse packets pass unchanged.
88
+ if (this.privatePrefix.startsWith(text) || (text.length < 64 && /^\x1b\[\?[0-9;\$]*$/.test(text)) || text === ESC+'[') break;
89
+ // Forward ordinary input in spans; large pastes must not become quadratic.
90
+ const next = this.pending.indexOf(0x1b, 1);
91
+ const count = next < 0 ? this.pending.length : next;
92
+ out.push(this.pending.subarray(0,count));this.pending=this.pending.subarray(count);
93
+ }
94
+ return Buffer.concat(out);
95
+ }
96
+ flush() { const data=this.pending;this.pending=Buffer.alloc(0);return data; }
97
+ }
98
+ export async function captureTerminalModes(input, output, {timeout=1000, probe=new ModeProbe()}={}) {
99
+ const buffered=[];
100
+ if (!input.isTTY || !output.isTTY) {
101
+ probe.replyDeadline = 1;
102
+ return {state:probe.state, probe, buffered:Buffer.alloc(0)};
103
+ }
104
+ const raw=!!input.isRaw;
105
+ input.setRawMode(true);
106
+ await new Promise(resolve=>{
107
+ let finished=false;
108
+ const finish=()=>{if(finished)return;finished=true;clearTimeout(fallback);clearTimeout(end);input.off('data',data);resolve();};
109
+ const data=chunk=>{const rest=probe.consume(chunk);if(rest.length)buffered.push(rest);if(probe.complete)finish();};
110
+ const fallback=setTimeout(()=>output.write(probe.standardQuery()),Math.min(200,timeout/2));
111
+ const end=setTimeout(finish,timeout);
112
+ input.on('data',data);input.resume();output.write(probe.privateQuery());
113
+ });
114
+ input.pause();input.setRawMode(raw);
115
+ buffered.push(probe.flush());
116
+ return {state:validateSnapshot(probe.state),probe,buffered:Buffer.concat(buffered)};
117
+ }
@@ -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.1.16",
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.1.16",
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.1.16",
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
+ }