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
@@ -561,6 +561,26 @@
561
561
  state.renderBound = state.renderDisposables.length > 0;
562
562
  }
563
563
 
564
+ // Touch gestures report mouse input without DOM mousedown: xterm's DOM
565
+ // handler focuses its hidden textarea, which can zoom Android WebView.
566
+ function reportTouchMouse(type, point, button = 0) {
567
+ const core = terminal?.__gottyXterm?._core;
568
+ if (!core?.coreMouseService?.areMouseEventsActive) return;
569
+ const screen = core.screenElement;
570
+ if (!screen) return;
571
+ const rect = screen.getBoundingClientRect();
572
+ const coords = core._mouseService.getMouseReportCoords({
573
+ clientX: Math.max(rect.left, Math.min(rect.right - 1, point.clientX)),
574
+ clientY: Math.max(rect.top, Math.min(rect.bottom - 1, point.clientY)),
575
+ }, screen);
576
+ if (!coords) return;
577
+ core.coreMouseService.triggerMouseEvent({
578
+ ...coords, button,
579
+ action: type === "mousedown" ? 1 : type === "mousemove" ? 32 : 0,
580
+ ctrl: false, alt: false, shift: false,
581
+ });
582
+ }
583
+
564
584
  function dispatchSyntheticWheel(target, touch, deltaX, deltaY) {
565
585
  if (!target) {
566
586
  return;
@@ -597,22 +617,69 @@
597
617
  return screen || viewport || xterm || terminal;
598
618
  };
599
619
 
620
+ let lastTap = null;
621
+ let drag = null;
622
+ const mouse = (type, point) => reportTouchMouse(type, point);
623
+ const releaseDrag = () => {
624
+ if (drag) {
625
+ const point = drag;
626
+ drag = null;
627
+ mouse("mouseup", point);
628
+ }
629
+ };
630
+ const cancelGesture = () => {
631
+ releaseDrag();
632
+ lastTap = null;
633
+ state.touchScroll = null;
634
+ };
635
+ state.cancelTouchGesture = cancelGesture;
636
+ bindTarget.addEventListener("contextmenu", (event) => {
637
+ if (drag) { event.preventDefault(); event.stopImmediatePropagation(); }
638
+ }, true);
639
+ window.addEventListener("blur", cancelGesture);
640
+ document.addEventListener("visibilitychange", () => {
641
+ if (document.hidden) cancelGesture();
642
+ });
643
+
600
644
  bindTarget.addEventListener("touchstart", (event) => {
601
645
  if (event.touches.length !== 1) {
602
- state.touchScroll = null;
646
+ cancelGesture();
603
647
  return;
604
648
  }
605
649
  const touch = event.touches[0];
650
+ const reporting = terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive;
651
+ if (reporting && window.touchDoubleTapDrag !== false && lastTap &&
652
+ performance.now() - lastTap.time < 350 &&
653
+ Math.hypot(touch.clientX - lastTap.x, touch.clientY - lastTap.y) < 24) {
654
+ lastTap = null;
655
+ state.touchScroll = null;
656
+ drag = { identifier: touch.identifier, clientX: touch.clientX, clientY: touch.clientY };
657
+ // Cancel compatibility mouse events: this is the second press, not a third click.
658
+ event.preventDefault();
659
+ mouse("mousedown", drag);
660
+ return;
661
+ }
662
+ lastTap = null;
606
663
  state.touchScroll = {
664
+ startX: touch.clientX, startY: touch.clientY, startTime: performance.now(), moved: false,
607
665
  identifier: touch.identifier,
608
666
  lastX: touch.clientX,
609
667
  lastY: touch.clientY,
610
668
  residualX: 0,
611
669
  residualY: 0
612
670
  };
613
- }, { passive: true });
671
+ }, { passive: false });
614
672
 
615
673
  bindTarget.addEventListener("touchmove", (event) => {
674
+ if (drag) {
675
+ const touch = Array.from(event.touches).find(t => t.identifier === drag.identifier);
676
+ if (!touch || event.touches.length !== 1) { cancelGesture(); return; }
677
+ event.preventDefault();
678
+ drag.clientX = touch.clientX;
679
+ drag.clientY = touch.clientY;
680
+ mouse("mousemove", drag);
681
+ return;
682
+ }
616
683
  if (!state.touchScroll) {
617
684
  return;
618
685
  }
@@ -621,6 +688,8 @@
621
688
  return;
622
689
  }
623
690
 
691
+ if (Math.hypot(touch.clientX - state.touchScroll.startX,
692
+ touch.clientY - state.touchScroll.startY) > 8) state.touchScroll.moved = true;
624
693
  const deltaX = state.touchScroll.lastX - touch.clientX;
625
694
  const deltaY = state.touchScroll.lastY - touch.clientY;
626
695
  state.touchScroll.lastX = touch.clientX;
@@ -653,11 +722,20 @@
653
722
  state.touchScroll.residualY -= ticksY * threshold * stepY;
654
723
  }, { passive: false });
655
724
 
656
- const clearTouchScroll = () => {
725
+ bindTarget.addEventListener("touchend", (event) => {
726
+ if (drag) {
727
+ event.preventDefault();
728
+ releaseDrag();
729
+ } else {
730
+ const scroll = state.touchScroll;
731
+ if (scroll && !scroll.moved && event.touches.length === 0 &&
732
+ performance.now() - scroll.startTime < 350) {
733
+ lastTap = { x: scroll.lastX, y: scroll.lastY, time: performance.now() };
734
+ }
735
+ }
657
736
  state.touchScroll = null;
658
- };
659
- bindTarget.addEventListener("touchend", clearTouchScroll, { passive: true });
660
- bindTarget.addEventListener("touchcancel", clearTouchScroll, { passive: true });
737
+ }, { passive: false });
738
+ bindTarget.addEventListener("touchcancel", cancelGesture, { passive: true });
661
739
  state.touchBound = true;
662
740
  }
663
741
 
@@ -676,9 +754,25 @@
676
754
  removeButton.textContent = "Remove image";
677
755
  menu.appendChild(removeButton);
678
756
 
757
+ const separator = document.createElement("hr");
758
+ const rightButton = document.createElement("button");
759
+ rightButton.type = "button";
760
+ rightButton.className = "kitty-context-menu__item";
761
+ rightButton.textContent = "Right click";
762
+ menu.append(separator, rightButton);
763
+ rightButton.addEventListener("click", (event) => {
764
+ event.preventDefault();
765
+ event.stopPropagation();
766
+ const point = state.contextMenu?.point;
767
+ hideContextMenu();
768
+ if (!point || !terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive) return;
769
+ reportTouchMouse("mousedown", { clientX: point.x, clientY: point.y }, 2);
770
+ reportTouchMouse("mouseup", { clientX: point.x, clientY: point.y }, 2);
771
+ });
772
+
679
773
  state.contextMenu = {
680
774
  root: menu,
681
- removeButton,
775
+ removeButton, rightButton, separator,
682
776
  targetKey: null
683
777
  };
684
778
 
@@ -722,6 +816,9 @@
722
816
  function showContextMenu(x, y, key) {
723
817
  const menu = ensureContextMenu();
724
818
  menu.targetKey = key;
819
+ menu.point = { x, y };
820
+ const reporting = !!terminal.__gottyXterm?._core?.coreMouseService?.areMouseEventsActive;
821
+ menu.rightButton.hidden = menu.separator.hidden = !reporting;
725
822
  menu.root.hidden = false;
726
823
 
727
824
  const margin = 10;
@@ -1004,6 +1101,15 @@
1004
1101
  return false;
1005
1102
  }
1006
1103
 
1104
+ const watchedSockets = new WeakSet();
1105
+ function watchTouchSocket(socket) {
1106
+ if (watchedSockets.has(socket)) return;
1107
+ watchedSockets.add(socket);
1108
+ socket.addEventListener("close", () => {
1109
+ if (state.socket === socket) state.cancelTouchGesture?.();
1110
+ });
1111
+ }
1112
+
1007
1113
  const nativeSend = WebSocket.prototype.send;
1008
1114
  const nativeOnOpen = WebSocket.prototype.addEventListener;
1009
1115
  WebSocket.prototype.addEventListener = function (type, listener, options) {
@@ -1012,6 +1118,7 @@
1012
1118
  }
1013
1119
  const wrapped = function (event) {
1014
1120
  state.socket = this;
1121
+ watchTouchSocket(this);
1015
1122
  inspectTerminalOutput(event.data);
1016
1123
  if (typeof event.data === "string" && event.data[0] === MSG_KITTY) {
1017
1124
  if (handleKittyMessage(event.data.slice(1))) {
@@ -1058,6 +1165,7 @@
1058
1165
  }
1059
1166
  const wrapped = function (event) {
1060
1167
  state.socket = this;
1168
+ watchTouchSocket(this);
1061
1169
  inspectTerminalOutput(event.data);
1062
1170
  if (typeof event.data === "string" && event.data[0] === MSG_KITTY) {
1063
1171
  if (handleKittyMessage(event.data.slice(1))) {
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+
3
+ // All flags defined by the kitty keyboard protocol. Kept unmasked so a CLI
4
+ // client on a real kitty terminal can restore them; the browser masks its own.
5
+ const KITTY_KEYBOARD_FLAGS = 0x1f;
6
+ const TRACKED_DEC_FLAGS = {
7
+ 1: "applicationCursorKeys",
8
+ 6: "origin",
9
+ 7: "wraparound",
10
+ 45: "reverseWraparound",
11
+ 66: "applicationKeypad",
12
+ 1004: "sendFocus",
13
+ 2004: "bracketedPasteMode",
14
+ };
15
+ const MOUSE_PROTOCOLS = { 9: "X10", 1000: "VT200", 1002: "DRAG", 1003: "ANY" };
16
+ const MOUSE_ENCODINGS = { 1006: "SGR", 1016: "SGR_PIXELS" };
17
+
18
+ // Mirrors the PTY's input/protocol modes so a resumed client can be brought
19
+ // back in sync. Mode semantics follow xterm.js, which renders the output.
20
+ class TerminalModeTracker {
21
+ constructor() {
22
+ this.reset();
23
+ this._state = "text";
24
+ this._seq = "";
25
+ }
26
+
27
+ reset() {
28
+ this.dec = {
29
+ applicationCursorKeys: false,
30
+ applicationKeypad: false,
31
+ bracketedPasteMode: false,
32
+ origin: false,
33
+ reverseWraparound: false,
34
+ sendFocus: false,
35
+ wraparound: true,
36
+ };
37
+ this.cursorHidden = false;
38
+ this.mouse = "NONE";
39
+ this.encoding = "DEFAULT";
40
+ this.alternate = false;
41
+ this.kitty = { normal: { flags: 0, stack: [] }, alternate: { flags: 0, stack: [] } };
42
+ }
43
+
44
+ snapshot() {
45
+ return {
46
+ dec: { ...this.dec },
47
+ cursorHidden: this.cursorHidden,
48
+ mouse: this.mouse,
49
+ encoding: this.encoding,
50
+ alternate: this.alternate,
51
+ kitty: {
52
+ normal: { flags: this.kitty.normal.flags, stack: [...this.kitty.normal.stack] },
53
+ alternate: { flags: this.kitty.alternate.flags, stack: [...this.kitty.alternate.stack] },
54
+ },
55
+ };
56
+ }
57
+
58
+ consume(buffer) {
59
+ // Control sequences are ASCII, so bytes can be scanned without UTF-8 decoding.
60
+ for (let i = 0; i < buffer.length; i += 1) {
61
+ const byte = buffer[i];
62
+ switch (this._state) {
63
+ case "text":
64
+ if (byte === 0x1b) {
65
+ this._state = "esc";
66
+ }
67
+ break;
68
+ case "esc":
69
+ if (byte === 0x5b) {
70
+ this._state = "csi";
71
+ this._seq = "";
72
+ } else if (byte === 0x5d || byte === 0x50 || byte === 0x5f || byte === 0x5e || byte === 0x58) {
73
+ this._state = "string";
74
+ this._osc = byte === 0x5d ? "" : null;
75
+ } else if (byte === 0x1b) {
76
+ this._state = "esc";
77
+ } else if (byte >= 0x20 && byte <= 0x2f) {
78
+ this._state = "esc-intermediate";
79
+ } else {
80
+ this._state = "text";
81
+ this.handleEsc(byte);
82
+ }
83
+ break;
84
+ case "esc-intermediate":
85
+ if (byte < 0x20 || byte > 0x2f) {
86
+ this._state = byte === 0x1b ? "esc" : "text";
87
+ }
88
+ break;
89
+ case "csi":
90
+ if (byte >= 0x40 && byte <= 0x7e) {
91
+ this._state = "text";
92
+ this.handleCsi(this._seq, String.fromCharCode(byte));
93
+ } else if (byte === 0x1b) {
94
+ this._state = "esc";
95
+ } else if (byte === 0x18 || byte === 0x1a || this._seq.length >= 64) {
96
+ this._state = "text";
97
+ } else if (byte >= 0x20) {
98
+ this._seq += String.fromCharCode(byte);
99
+ }
100
+ break;
101
+ case "string":
102
+ if (byte === 0x07 || byte === 0x18 || byte === 0x1a) {
103
+ if (byte === 0x07) this.handleOsc();
104
+ this._state = "text";
105
+ } else if (byte === 0x1b) {
106
+ this._state = "string-esc";
107
+ } else if (this._osc != null) {
108
+ this._osc = this._osc.length < 256 ? this._osc + String.fromCharCode(byte) : null;
109
+ }
110
+ break;
111
+ case "string-esc":
112
+ if (byte === 0x5c) this.handleOsc();
113
+ else this._osc = null;
114
+ this._state = byte === 0x1b ? "string-esc" : byte === 0x5c ? "text" : "string";
115
+ break;
116
+ }
117
+ }
118
+ }
119
+
120
+ handleOsc() {
121
+ const match = /^777;jsgotty-kitty-v1;(normal|alternate);(\d{1,2});(\d{1,2}(?:,\d{1,2})*)?$/.exec(this._osc || '');
122
+ if (!match) return;
123
+ const flags = +match[2], stack = match[3] ? match[3].split(',').map(Number) : [];
124
+ if (flags > 31 || stack.length > 32 || stack.some(n=>n > 31)) return;
125
+ this.kitty[match[1]] = {flags, stack};
126
+ }
127
+
128
+ handleEsc(byte) {
129
+ if (byte === 0x3d) {
130
+ this.dec.applicationKeypad = true;
131
+ } else if (byte === 0x3e) {
132
+ this.dec.applicationKeypad = false;
133
+ } else if (byte === 0x63) {
134
+ this.reset();
135
+ }
136
+ }
137
+
138
+ handleCsi(seq, final) {
139
+ const prefix = /^[<=>?]/.test(seq) ? seq[0] : "";
140
+ const body = prefix ? seq.slice(1) : seq;
141
+ if (!/^[0-9;:]*$/.test(body)) {
142
+ if (body === "!" && final === "p") {
143
+ this.softReset();
144
+ }
145
+ return;
146
+ }
147
+ const params = body === "" ? [] : body.split(";").map((part) => {
148
+ const value = Number.parseInt(part, 10);
149
+ return Number.isFinite(value) ? value : 0;
150
+ });
151
+ if (prefix === "?" && (final === "h" || final === "l")) {
152
+ for (const mode of params) {
153
+ this.setPrivateMode(mode, final === "h");
154
+ }
155
+ } else if (final === "u" && prefix !== "?") {
156
+ this.handleKittyKeyboard(prefix, params);
157
+ }
158
+ }
159
+
160
+ softReset() {
161
+ // DECSTR keeps the active screen and mouse reporting, like xterm.js.
162
+ const { alternate, mouse, encoding } = this;
163
+ this.reset();
164
+ Object.assign(this, { alternate, mouse, encoding });
165
+ }
166
+
167
+ setPrivateMode(mode, enabled) {
168
+ if (TRACKED_DEC_FLAGS[mode]) {
169
+ this.dec[TRACKED_DEC_FLAGS[mode]] = enabled;
170
+ } else if (MOUSE_PROTOCOLS[mode]) {
171
+ this.mouse = enabled ? MOUSE_PROTOCOLS[mode] : "NONE";
172
+ } else if (MOUSE_ENCODINGS[mode]) {
173
+ this.encoding = enabled ? MOUSE_ENCODINGS[mode] : "DEFAULT";
174
+ } else if (mode === 25) {
175
+ this.cursorHidden = !enabled;
176
+ } else if (mode === 47 || mode === 1047 || mode === 1049) {
177
+ this.alternate = enabled;
178
+ }
179
+ }
180
+
181
+ handleKittyKeyboard(prefix, params) {
182
+ const state = this.kitty[this.alternate ? "alternate" : "normal"];
183
+ const value = (index, fallback = 0) => (Number.isInteger(params[index]) ? params[index] : fallback);
184
+ if (prefix === ">") {
185
+ state.stack.push(state.flags);
186
+ if (state.stack.length > 32) {
187
+ state.stack.shift();
188
+ }
189
+ state.flags = value(0) & KITTY_KEYBOARD_FLAGS;
190
+ } else if (prefix === "<") {
191
+ let count = Math.min(value(0) || 1, 33);
192
+ while (count--) {
193
+ state.flags = state.stack.pop() ?? 0;
194
+ }
195
+ } else if (prefix === "=") {
196
+ const flags = value(0) & KITTY_KEYBOARD_FLAGS;
197
+ const mode = value(1) || 1;
198
+ if (mode === 1) {
199
+ state.flags = flags;
200
+ } else if (mode === 2) {
201
+ state.flags |= flags;
202
+ } else if (mode === 3) {
203
+ state.flags &= ~flags;
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ const DEFAULT_MODES = new TerminalModeTracker().snapshot();
210
+ const MOUSE_PROTOCOL_MODES = { X10: 9, VT200: 1000, DRAG: 1002, ANY: 1003 };
211
+ const MOUSE_ENCODING_MODES = { SGR: 1006, SGR_PIXELS: 1016 };
212
+
213
+ function sameKitty(a, b) {
214
+ return a.flags === b.flags && a.stack.length === b.stack.length &&
215
+ a.stack.every((flags, index) => flags === b.stack[index]);
216
+ }
217
+
218
+ // Work only on the observed stack; never pop an arbitrary number of entries.
219
+ // With a terminal that cannot report its stack, [] denotes the unseen baseline.
220
+ function kittyTransition(from, to) {
221
+ if (to.flags == null) return "";
222
+ let common = 0;
223
+ while (common < from.stack.length && common < to.stack.length &&
224
+ from.stack[common] === to.stack[common]) common++;
225
+ let sequence = "";
226
+ let flags = from.flags;
227
+ const pops = from.stack.length - common;
228
+ if (pops) {
229
+ sequence += `\x1b[<${pops}u`;
230
+ flags = from.stack[common];
231
+ }
232
+ const first = to.stack[common] ?? to.flags;
233
+ if (flags !== first) sequence += `\x1b[=${first};1u`;
234
+ for (let i = common; i < to.stack.length; i++)
235
+ sequence += `\x1b[>${to.stack[i + 1] ?? to.flags}u`;
236
+ return sequence;
237
+ }
238
+
239
+ // Escape sequence that moves a terminal known to be in `from` to `to`, emitting
240
+ // only differences: DECOM homes the cursor and 1049 overwrites the saved cursor.
241
+ function modeTransition(from, to = DEFAULT_MODES, { normalizeMouse = false, kittyRestore = false } = {}) {
242
+ let sequence = "";
243
+ const fromScreen = from.alternate ? "alternate" : "normal";
244
+ const toScreen = to.alternate ? "alternate" : "normal";
245
+ // Kitty keyboard stacks are per screen, so each is fixed while it is active.
246
+ if (from.alternate != null && !sameKitty(from.kitty[fromScreen], to.kitty[fromScreen])) {
247
+ sequence += kittyTransition(from.kitty[fromScreen], to.kitty[fromScreen]);
248
+ }
249
+ if (to.alternate != null && from.alternate !== to.alternate) {
250
+ sequence += `\x1b[?1049${to.alternate ? "h" : "l"}`;
251
+ if (!sameKitty(from.kitty[toScreen], to.kitty[toScreen])) {
252
+ sequence += kittyTransition(from.kitty[toScreen], to.kitty[toScreen]);
253
+ }
254
+ }
255
+ const inactive = toScreen === "normal" ? "alternate" : "normal";
256
+ // Switching screens just to edit a hidden stack destroys alternate contents.
257
+ // Only emit this extension after the actual terminal advertised support.
258
+ const hidden = to.kitty[inactive];
259
+ if (kittyRestore && from.alternate != null && to.alternate != null && fromScreen === toScreen &&
260
+ !sameKitty(from.kitty[inactive], hidden) && hidden.flags != null && hidden.stack.every(Number.isInteger)) {
261
+ sequence += `\x1b]777;jsgotty-kitty-v1;${inactive};${hidden.flags};${hidden.stack.join(',')}\x07`;
262
+ }
263
+ for (const [mode, name] of Object.entries(TRACKED_DEC_FLAGS)) {
264
+ if (to.dec[name] == null || from.dec[name] === to.dec[name]) {
265
+ continue;
266
+ }
267
+ if (name === "applicationKeypad") {
268
+ sequence += to.dec[name] ? "\x1b=" : "\x1b>"; // more widely supported than ?66
269
+ } else {
270
+ sequence += `\x1b[?${mode}${to.dec[name] ? "h" : "l"}`;
271
+ }
272
+ }
273
+ if (to.cursorHidden != null && from.cursorHidden !== to.cursorHidden) {
274
+ sequence += `\x1b[?25${to.cursorHidden ? "l" : "h"}`;
275
+ }
276
+ // Raw query results preserve independent switches on native terminals. Reset
277
+ // first, then enable in priority order so exclusive terminals also end in the
278
+ // intended effective mode. Unanswered switches are never guessed.
279
+ for (const [name, bitsName, table] of [
280
+ ['mouse', 'mouseModeBits', MOUSE_PROTOCOL_MODES],
281
+ ['encoding', 'mouseEncodingBits', MOUSE_ENCODING_MODES],
282
+ ]) {
283
+ const bits = to[bitsName];
284
+ if (bits) {
285
+ for (const mode of Object.values(table))
286
+ if (bits[mode] === false) sequence += `\x1b[?${mode}l`;
287
+ for (const mode of Object.values(table))
288
+ if (bits[mode] === true) sequence += `\x1b[?${mode}h`;
289
+ } else if (to[name] != null && (normalizeMouse || from[name] !== to[name])) {
290
+ for (const mode of Object.values(table)) sequence += `\x1b[?${mode}l`;
291
+ if (table[to[name]]) sequence += `\x1b[?${table[to[name]]}h`;
292
+ }
293
+ }
294
+ return sequence;
295
+ }
296
+
297
+ function validateSnapshot(value) {
298
+ if (!value || !value.dec || !value.kitty) throw Error("Invalid terminal modes");
299
+ const result = new TerminalModeTracker().snapshot();
300
+ for (const key of Object.values(TRACKED_DEC_FLAGS)) {
301
+ if (typeof value.dec[key] !== 'boolean' && value.dec[key] !== null) throw Error("Invalid DEC mode");
302
+ result.dec[key] = value.dec[key];
303
+ }
304
+ for (const key of ['alternate', 'cursorHidden']) {
305
+ if (typeof value[key] !== 'boolean' && value[key] !== null) throw Error("Invalid screen mode");
306
+ result[key] = value[key];
307
+ }
308
+ for (const [key, choices] of [['mouse', ['NONE', ...Object.values(MOUSE_PROTOCOLS)]], ['encoding', ['DEFAULT', ...Object.values(MOUSE_ENCODINGS)]]]) {
309
+ if (value[key] !== null && !choices.includes(value[key])) throw Error("Invalid mouse mode");
310
+ result[key] = value[key];
311
+ }
312
+ for (const [key, table] of [['mouseModeBits', MOUSE_PROTOCOLS], ['mouseEncodingBits', MOUSE_ENCODINGS]]) {
313
+ if (value[key] === undefined) continue;
314
+ if (!value[key] || typeof value[key] !== 'object') throw Error('Invalid mouse bits');
315
+ result[key] = {};
316
+ for (const mode of Object.keys(table)) {
317
+ const bit = value[key][mode];
318
+ if (bit !== null && typeof bit !== 'boolean') throw Error('Invalid mouse bit');
319
+ result[key][mode] = bit;
320
+ }
321
+ }
322
+ for (const key of ['normal', 'alternate']) {
323
+ const state = value.kitty[key];
324
+ const flag = n => Number.isInteger(n) && n >= 0 && n <= KITTY_KEYBOARD_FLAGS;
325
+ if (!state || (state.flags !== null && !flag(state.flags)) || !Array.isArray(state.stack) ||
326
+ state.stack.length > 32 || !state.stack.every(n => n === null || flag(n))) throw Error("Invalid Kitty state");
327
+ result.kitty[key] = {flags: state.flags, stack: [...state.stack]};
328
+ }
329
+ return result;
330
+ }
331
+
332
+ module.exports = { TerminalModeTracker, modeTransition, validateSnapshot, TRACKED_DEC_FLAGS, MOUSE_PROTOCOLS, MOUSE_ENCODINGS };
@@ -0,0 +1,115 @@
1
+ const assert = require('node:assert/strict');
2
+ const test = require('node:test');
3
+ const fs = require('node:fs');
4
+ const vm = require('node:vm');
5
+ const path = require('node:path');
6
+
7
+ // Exercise the shipped bundle's wrapper method, without a DOM or new dependencies.
8
+ const bundle = fs.readFileSync(path.join(__dirname, '../static/js/gotty.js'), 'utf8');
9
+ const start = bundle.indexOf(' handleAndroidAltKey(event) {');
10
+ assert.ok(start >= 0);
11
+ const end = bundle.indexOf(' info() {', start);
12
+ assert.ok(end > start);
13
+ const method = bundle.slice(start, end);
14
+ function setup(userAgent = 'Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 wv') {
15
+ const handler = vm.runInNewContext(`({${method}}).handleAndroidAltKey`, {
16
+ navigator: { userAgent },
17
+ });
18
+ const sent = [];
19
+ const target = { term: { input: (text, userInput) => sent.push([text, userInput]) } };
20
+ function dispatch(overrides = {}) {
21
+ const event = {
22
+ type: 'keydown', code: 'KeyE', key: 'Dead', keyCode: 69,
23
+ altKey: true, ctrlKey: false, metaKey: false, shiftKey: false,
24
+ isComposing: false, getModifierState: () => false,
25
+ prevented: false, stopped: false,
26
+ preventDefault() { this.prevented = true; },
27
+ stopPropagation() { this.stopped = true; },
28
+ ...overrides,
29
+ };
30
+ return { result: handler.call(target, event), event };
31
+ }
32
+ return { sent, dispatch };
33
+ }
34
+
35
+ test('Android accent shortcuts emit one ESC sequence; keypress/keyup do not duplicate it', () => {
36
+ for (const letter of ['E', 'I', 'N', 'U']) {
37
+ for (const keyCode of [letter.charCodeAt(0), 229]) {
38
+ const { sent, dispatch } = setup();
39
+ const { event, result } = dispatch({ code: `Key${letter}`, keyCode });
40
+ assert.equal(result, false);
41
+ assert.equal(event.prevented, true);
42
+ assert.equal(event.stopped, true);
43
+ dispatch({ type: 'keypress', code: `Key${letter}`, keyCode });
44
+ dispatch({ type: 'keyup', code: `Key${letter}`, keyCode });
45
+ assert.deepEqual(sent, [[`\x1b${letter.toLowerCase()}`, true]]);
46
+ // No dead-key state is retained in this handler for the next ordinary key.
47
+ assert.equal(dispatch({ altKey: false, key: 'e' }).result, true);
48
+ assert.equal(sent.length, 1);
49
+ }
50
+ }
51
+ });
52
+
53
+ test('all printable physical Alt keys bypass dead-key handling, including Shift and repeat', () => {
54
+ const { sent, dispatch } = setup();
55
+ for (const [code, shiftKey, expected] of [
56
+ ['KeyA', false, 'a'], ['KeyE', true, 'E'], ['Digit0', false, '0'],
57
+ ['Digit1', true, '!'], ['Digit0', true, ')'], ['Quote', false, "'"],
58
+ ['Backquote', true, '~'], ['Backslash', false, '\\'], ['Space', false, ' '],
59
+ ]) {
60
+ dispatch({ code, shiftKey, key: 'Dead' });
61
+ assert.deepEqual(sent.at(-1), [`\x1b${expected}`, true]);
62
+ }
63
+ dispatch({ code: 'KeyE', key: 'é', repeat: true });
64
+ assert.deepEqual(sent.at(-1), ['\x1be', true]);
65
+ });
66
+
67
+ test('composition, AltGr, other modifiers, and unmapped keys retain existing handling', () => {
68
+ for (const overrides of [
69
+ { isComposing: true, keyCode: 229 }, { getModifierState: key => key === 'AltGraph' },
70
+ { altKey: false }, { ctrlKey: true }, { metaKey: true },
71
+ { code: '', keyCode: 229 }, { code: 'ArrowLeft' }, { code: 'Enter' },
72
+ { code: 'Unidentified', keyCode: 229 }, { code: 'toString' },
73
+ { type: 'keyup', key: 'e' },
74
+ ]) {
75
+ const { sent, dispatch } = setup();
76
+ const { result, event } = dispatch(overrides);
77
+ assert.equal(result, true);
78
+ assert.equal(event.prevented, false);
79
+ assert.deepEqual(sent, []);
80
+ }
81
+ });
82
+
83
+ test('non-Android browsers retain existing Alt/dead-key handling', () => {
84
+ for (const ua of ['Mozilla/5.0 (X11; Linux x86_64)', 'Mozilla/5.0 (Macintosh)', 'Windows']) {
85
+ const { sent, dispatch } = setup(ua);
86
+ assert.equal(dispatch().result, true);
87
+ assert.deepEqual(sent, []);
88
+ }
89
+ });
90
+
91
+ // Replay the actual Android 16 WebView trace: no letter keydown and empty code.
92
+ test('Android keyup-only dead keys use legacy keyCode, and ordinary IME input stays untouched', () => {
93
+ const { sent, dispatch } = setup();
94
+ for (const keyCode of [69, 78, 85, 73]) {
95
+ dispatch({ type: 'keydown', code: '', key: 'Alt', keyCode: 18 });
96
+ assert.equal(dispatch({ type: 'keyup', code: '', key: 'Dead', keyCode }).result, false);
97
+ dispatch({ type: 'keyup', code: '', key: 'Alt', keyCode: 18 });
98
+ }
99
+ assert.deepEqual(sent, ['e', 'n', 'u', 'i'].map(c => ['\x1b' + c, true]));
100
+ assert.equal(dispatch({ code: '', key: 'Unidentified', keyCode: 229, altKey: false }).result, true);
101
+ assert.equal(sent.length, 4);
102
+ });
103
+
104
+ test('legacy keydown plus dead keyup emits once, even with different code availability', () => {
105
+ for (const [downCode, upCode] of [['', ''], ['KeyE', ''], ['', 'KeyE']]) {
106
+ const { sent, dispatch } = setup();
107
+ dispatch({ code: downCode, keyCode: 69 });
108
+ dispatch({ type: 'keypress', code: downCode, keyCode: 69 });
109
+ dispatch({ type: 'keyup', code: upCode, keyCode: 69 });
110
+ assert.deepEqual(sent, [['\x1be', true]]);
111
+ // The next keyup-only shortcut must not be suppressed by the preceding press.
112
+ dispatch({ type: 'keyup', code: '', keyCode: 69 });
113
+ assert.equal(sent.length, 2);
114
+ }
115
+ });