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
@@ -0,0 +1,167 @@
1
+ import {test,expect} from "bun:test";
2
+ import {loadCanvasKit,loadFontSet} from "../initramfs/lib/canvas.js";
3
+ import {Renderer} from "../initramfs/lib/bunterm/renderer.js";
4
+ import {Terminal} from "../initramfs/lib/xterm/xterm-headless.mjs";
5
+ import {createVtMenu} from "../initramfs/lib/bunterm/vt-menu.js";
6
+
7
+ test("real CanvasKit redraws resized text and overlay with fresh font metrics", async () => {
8
+ const CanvasKit=await loadCanvasKit(), fonts=loadFontSet(CanvasKit,"terminal");
9
+ const surface=CanvasKit.MakeSurface(800,480);
10
+ const screen={width:800,height:480,canvas:surface.getCanvas(),flush:()=>surface.flush()};
11
+ const renderer=new Renderer({CanvasKit,screen,fonts});
12
+ const term=new Terminal({cols:renderer.columns,rows:renderer.rows,allowProposedApi:true});
13
+ try {
14
+ await new Promise(resolve=>term.write("Hello 中文 👨‍👩‍👧\r\nSelection and font resize",resolve));
15
+ const columns=renderer.columns;
16
+ renderer.render(term);
17
+ renderer.setFontSize(20);
18
+ expect(renderer.columns).toBeLessThan(columns);
19
+ term.resize(renderer.columns,renderer.rows);
20
+ const menu=createVtMenu({list:()=>[{number:1,label:"tty1 — initial console"}],select(){},changed(){renderer.invalidate();},
21
+ width:800,height:480,rowHeight:renderer.cellHeight,
22
+ controls:{fontSize:()=>renderer.fontSize,cellHeight:()=>renderer.cellHeight,execute:()=>""}});
23
+ menu.pointer({x:0,y:0,pressed:["left"],buttons:{left:true},wheel:0});
24
+ renderer.render(term,{overlay:()=>menu.draw(renderer)});
25
+ const image=surface.makeImageSnapshot();
26
+ expect(image.width()).toBe(800);
27
+ image.delete();
28
+ renderer.setFontSize(13);
29
+ expect(renderer.columns).toBe(columns);
30
+ expect(()=>renderer.setFontSize(NaN)).toThrow();
31
+ } finally {term.dispose();renderer.delete();fonts.delete();surface.delete();}
32
+ },20000);
33
+
34
+ test("local pointer restores exact pixels across images, edges, text and font changes", async () => {
35
+ const {openCanvas}=await import('../initramfs/lib/canvas.js');
36
+ const CK=await loadCanvasKit(), fonts=loadFontSet(CK,'terminal');
37
+ const make=()=>{
38
+ const display={width:320,height:200,stride:1296,bitsPerPixel:32,
39
+ fields:{red:{offset:16,length:8},green:{offset:8,length:8},blue:{offset:0,length:8}},pixels:new Uint8Array(1296*200)};
40
+ const screen=openCanvas(CK,display);screen.flush=()=>screen.surface.flush();
41
+ return screen;
42
+ };
43
+ const screen=make(),reference=make();
44
+ reference.drawOpaqueImage = undefined;
45
+ const r=new Renderer({CanvasKit:CK,screen,fonts}), ref=new Renderer({CanvasKit:CK,screen:reference,fonts});
46
+ const term=new Terminal({cols:r.columns,rows:r.rows,allowProposedApi:true});
47
+ const imageSurface=CK.MakeSurface(37,29);imageSurface.getCanvas().clear(CK.Color(30,90,170));
48
+ const image=imageSurface.makeImageSnapshot(), images=[{image,rect:[0,20,320,200]}];
49
+ let imageDraws=0;const draw=r.drawImages.bind(r);r.drawImages=(...args)=>{imageDraws++;return draw(...args);};
50
+ const check=(pointer,cursor={x:1,y:0,visible:true})=>{
51
+ const options={pointer,cursor,images};r.render(term,options);ref.invalidate();ref.render(term,options);
52
+ expect(screen.saveRegion(0,0,320,200).pixels).toEqual(reference.saveRegion(0,0,320,200).pixels);
53
+ };
54
+ try {
55
+ await new Promise(resolve=>term.write('Hello world',resolve));
56
+ check({x:100,y:100});const initial=imageDraws;
57
+ for(const point of [{x:101,y:101},{x:0,y:0},{x:319,y:199},{x:150.5,y:78.5},null])check(point);
58
+ expect(imageDraws).toBe(initial);
59
+ check({x:20,y:20});
60
+ await new Promise(resolve=>term.write('\rChanged',resolve));check({x:20,y:20});
61
+ check({x:50,y:50},{x:2,y:0,visible:false});
62
+ r.setFontSize(24);ref.setFontSize(24);term.resize(r.columns,r.rows);check({x:318,y:198});
63
+ check(null);
64
+ } finally {term.dispose();r.delete();ref.delete();image.delete();imageSurface.delete();screen.close();reference.close();fonts.delete();}
65
+ },20000);
66
+
67
+ test('opaque copies match Skia for clipped crops and dirty bands; transparency and scaling fall back', async()=>{
68
+ const {openCanvas}=await import('../initramfs/lib/canvas.js');
69
+ const CK=await loadCanvasKit();
70
+ const make=()=>openCanvas(CK,{width:64,height:48,stride:272,bitsPerPixel:32,
71
+ fields:{red:{offset:16,length:8},green:{offset:8,length:8},blue:{offset:0,length:8}},pixels:new Uint8Array(272*48)});
72
+ const a=make(),b=make();
73
+ const data=new Uint8Array(32*24*4);for(let i=0;i<data.length;i+=4){data[i]=i%251;data[i+1]=(i*7)%253;data[i+2]=(i*13)%255;data[i+3]=255;}
74
+ const info={width:32,height:24,colorType:CK.ColorType.RGBA_8888,alphaType:CK.AlphaType.Unpremul,colorSpace:CK.ColorSpace.SRGB};
75
+ const image=CK.MakeImage(info,data,128);
76
+ const bands=[[0,9],[12,35]];
77
+ try {
78
+ for(const rect of [[5,4,37,28],[-5,-3,27,21],[55,39,87,63]]){
79
+ a.canvas.clear(CK.Color(7,11,13));b.canvas.clear(CK.Color(7,11,13));
80
+ expect(a.drawOpaqueImage(image,rect,[0,0,32,24],bands)).toBe(true);
81
+ for(const [y0,y1] of bands){b.canvas.save();b.canvas.clipRect(CK.LTRBRect(0,y0,64,y1),CK.ClipOp.Intersect,false);b.canvas.drawImageRectOptions(image,CK.LTRBRect(0,0,32,24),CK.LTRBRect(...rect),CK.FilterMode.Linear,CK.MipmapMode.Linear,null);b.canvas.restore();}
82
+ expect(a.saveRegion(0,0,64,48).pixels).toEqual(b.saveRegion(0,0,64,48).pixels);
83
+ }
84
+ expect(a.drawOpaqueImage(image,[0,0,64,48],[0,0,32,24],bands)).toBe(false);
85
+ expect(a.drawOpaqueImage(image,[0.5,0,32.5,24],[0,0,32,24],bands)).toBe(false);
86
+ data[3]=100;const alpha=CK.MakeImage(info,data,128);
87
+ try{expect(a.drawOpaqueImage(alpha,[0,0,32,24],[0,0,32,24],bands)).toBe(false);}finally{alpha.delete();}
88
+ }finally{image.delete();a.close();b.close();}
89
+ },20000);
90
+
91
+ test('opaque image readback reuses WASM destination and falls back when native BGRA is rejected',async()=>{
92
+ const {openCanvas}=await import('../initramfs/lib/canvas.js');const CK=await loadCanvasKit();
93
+ const screen=openCanvas(CK,{width:8,height:8,stride:32,bitsPerPixel:32,
94
+ fields:{red:{offset:16,length:8},green:{offset:8,length:8},blue:{offset:0,length:8}},pixels:new Uint8Array(256)});
95
+ const info={width:8,height:8,colorType:CK.ColorType.RGBA_8888,alphaType:CK.AlphaType.Unpremul,colorSpace:CK.ColorSpace.SRGB};
96
+ const bytes=new Uint8Array(256);for(let i=0;i<256;i+=4)bytes.set([201,71,19,255],i);
97
+ const images=[],destinations=[];
98
+ try{
99
+ for(const fallback of [false,true]){
100
+ const image=CK.MakeImage(info,bytes,32);images.push(image);
101
+ const read=image.readPixels.bind(image);let reads=0;
102
+ image.readPixels=(x,y,format,destination,stride)=>{reads++;expect(destination).toBeTruthy();destinations.push(destination);
103
+ if(fallback && format.colorType===CK.ColorType.BGRA_8888)return null;
104
+ return read(x,y,format,destination,stride);};
105
+ for(let n=0;n<2;n++)expect(screen.drawOpaqueImage(image,[0,0,8,8],[0,0,8,8],[[0,8]])).toBe(true);
106
+ expect(reads).toBe(fallback?2:1);
107
+ const actual=screen.saveRegion(0,0,8,8).pixels;
108
+ for(let i=0;i<256;i+=4)expect([...actual.subarray(i,i+4)]).toEqual([19,71,201,255]);
109
+ }
110
+ expect(destinations.every(d=>d===destinations[0])).toBe(true);
111
+ }finally{images.forEach(i=>i.delete());screen.close();}
112
+ },20000);
113
+
114
+ test('raw images bypass Skia allocation/readback; covered text, removal, crops and fallback match full repaint', async()=>{
115
+ const {openCanvas}=await import('../initramfs/lib/canvas.js');
116
+ const {decodeImage}=await import('../initramfs/lib/bunterm/images.js');
117
+ const {skiaImage,rawImageData}=await import('../initramfs/lib/raw-image.js');
118
+ const CK=await loadCanvasKit(),fonts=loadFontSet(CK,'terminal');
119
+ const make=()=>{const s=openCanvas(CK,{width:320,height:200,stride:1296,bitsPerPixel:32,
120
+ fields:{red:{offset:16,length:8},green:{offset:8,length:8},blue:{offset:0,length:8}},pixels:new Uint8Array(1296*200)});s.flush=()=>s.surface.flush();return s;};
121
+ const screen=make(),reference=make();reference.drawOpaqueImage=undefined;reference.canDrawRawImage=undefined;
122
+ const r=new Renderer({CanvasKit:CK,screen,fonts}),ref=new Renderer({CanvasKit:CK,screen:reference,fonts});
123
+ const term=new Terminal({cols:r.columns,rows:r.rows,allowProposedApi:true});
124
+ const handles=[];let allocations=0,draws=0;
125
+ const makeImage=CK.MakeImage.bind(CK),drawRow=r.drawRow.bind(r);
126
+ const proxy=Object.create(CK);proxy.MakeImage=(...args)=>{allocations++;return makeImage(...args);};
127
+ r.drawRow=(...args)=>{draws++;return drawRow(...args);};
128
+ const create=async(format=0x42475241,alpha=255)=>{
129
+ const bytes=new Uint8Array(320*200*4);
130
+ for(let i=0;i<bytes.length;i+=4)bytes.set([i%251,83,211,alpha],i);
131
+ const handle=await decodeImage(proxy,{format,width:320,height:200,data:bytes},{deferRaw:true});handles.push(handle);
132
+ return handle;
133
+ };
134
+ const check=(image,rect=[0,0,320,200],source=[0,0,320,200],pointer=null,invalidate=true)=>{
135
+ const images=image?[{image,rect,source}]:[];
136
+ if(invalidate)r.invalidate();r.render(term,{images,pointer,cursor:{x:1,y:1,visible:true}});
137
+ const before=allocations;
138
+ ref.invalidate();ref.render(term,{images:images.map(p=>({...p,image:skiaImage(p.image)})),pointer,cursor:{x:1,y:1,visible:true}});
139
+ expect(screen.saveRegion(0,0,320,200).pixels).toEqual(reference.saveRegion(0,0,320,200).pixels);
140
+ return before;
141
+ };
142
+ try{
143
+ await new Promise(resolve=>term.write('Hello 中文\r\nHidden text',resolve));
144
+ const raw=await create();
145
+ expect(allocations).toBe(0);expect(check(raw)).toBe(0);expect(draws).toBe(0);
146
+ // Editing covered text must be visible once the image disappears.
147
+ await new Promise(resolve=>term.write('\rChanged underlying text',resolve));
148
+ check(raw,[0,0,320,200],[0,0,320,200],{x:100,y:100});
149
+ check(null,undefined,undefined,null,false);expect(draws).toBeGreaterThan(0);
150
+ // Casty leaves one column free. Hidden glyphs may be skipped, but the
151
+ // visible edge, wide/italic glyphs and exposed text must match full repaint.
152
+ await new Promise(resolve=>term.write('\x1b[H\x1b[3m'+('abc中W '.repeat(60))+'\x1b[0m',resolve));
153
+ let cells=0;const drawCell=r.drawCell.bind(r);r.drawCell=(...args)=>{cells++;return drawCell(...args);};
154
+ const width=(r.columns-1)*r.cellWidth,height=(r.rows-1)*r.cellHeight;
155
+ check(raw,[r.offsetX,r.offsetY,r.offsetX+width,r.offsetY+height],[0,0,width,height]);
156
+ expect(cells).toBeLessThan(r.columns*r.rows);
157
+ check(raw,[r.offsetX+r.cellWidth,r.offsetY,r.offsetX+width,r.offsetY+height],[0,0,width-r.cellWidth,height]);
158
+ check(null,undefined,undefined,null,false);
159
+ check(raw,[-5,11,310,171],[5,20,320,180]);
160
+ check(raw,[0,0,160,100]); // scaling materializes lazily
161
+ check(await create(32)); // channel mismatch falls back
162
+ check(await create(0x42475241,100)); // transparency must paint underlying text
163
+ check(raw,[0.5,0,320.5,200]);
164
+ r.setFontSize(20);ref.setFontSize(20);term.resize(r.columns,r.rows);check(raw);check(null);
165
+ raw.delete();expect(rawImageData(raw)).toBeUndefined();raw.delete();
166
+ }finally{handles.forEach(i=>i.delete());term.dispose();r.delete();ref.delete();screen.close();reference.close();fonts.delete();}
167
+ },20000);
@@ -0,0 +1,38 @@
1
+ import {test,expect} from "bun:test";
2
+ import {Terminal} from "../initramfs/lib/xterm/xterm-headless.mjs";
3
+ import {createSelection} from "../initramfs/lib/bunterm/selection.js";
4
+ import {pasteText} from "../initramfs/lib/bunterm/clipboard.js";
5
+ const write = (term,text) => new Promise(resolve=>term.write(text,resolve));
6
+ const point = (x,y,pressed=[],released=[]) => ({x,y,pressed,released,moved:true});
7
+ test("selection joins wraps, includes wide graphemes and works backwards", async () => {
8
+ const term=new Terminal({cols:8,rows:4,allowProposedApi:true});
9
+ try {
10
+ await write(term,"ab中文efghij\r\nlast");
11
+ const s=createSelection(term,{offsetX:0,offsetY:0,cellWidth:1,cellHeight:1},()=>{});
12
+ s.pointer(point(3,0,["left"]));
13
+ s.pointer(point(1,1,[],["left"]));
14
+ expect(s.text()).toBe("中文efgh");
15
+ s.pointer(point(1,1,["left"]));
16
+ s.pointer(point(3,0,[],["left"]));
17
+ expect(s.text()).toBe("中文efgh");
18
+ s.pointer(point(0,1,["left"]));
19
+ s.pointer(point(3,2,[],["left"]));
20
+ expect(s.text()).toBe("ghij\nlast");
21
+ await write(term,"\x1b[?1000h");
22
+ expect(s.pointer(point(0,0,["left"]))).toBe(false);
23
+ expect(s.active).toBe(false);
24
+ } finally {term.dispose();}
25
+ });
26
+ test("paste uses one negotiated bracketed write, strips embedded ESC, normalizes LF", async () => {
27
+ const term=new Terminal({allowProposedApi:true}),out=[];
28
+ try {
29
+ pasteText(term,"a\nb\r\nc",s=>out.push(s));
30
+ expect(out.pop()).toBe("a\rb\rc");
31
+ await write(term,"\x1b[?2004h");
32
+ pasteText(term,"a\n\x1b[201~b",s=>out.push(s));
33
+ expect(out.pop()).toBe("\x1b[200~a\r[201~b\x1b[201~");
34
+ await write(term,"\x1b[?2004l");
35
+ pasteText(term,"text",s=>out.push(s));
36
+ expect(out).toEqual(["text"]);
37
+ } finally {term.dispose();}
38
+ });
@@ -0,0 +1,178 @@
1
+ import { test, expect } from "bun:test";
2
+ import { createVtMenu } from "../initramfs/lib/bunterm/vt-menu.js";
3
+ import { createCommands } from "../initramfs/lib/bunterm/commands.js";
4
+ const report = (x, y, pressed = [], released = []) => ({ x, y, pressed, released, buttons: {left: pressed.includes("left")}, wheel: 0, moved: false });
5
+ function fixture(extra = {}) {
6
+ const selected = [];
7
+ const menu = createVtMenu({ width: 800, height: 600, rowHeight: 16,
8
+ list: () => [{number:1,label:"initial"}, {number:3,label:"bunterm"}],
9
+ select: n => selected.push(n), changed() {}, ...extra });
10
+ return {menu, selected};
11
+ }
12
+ test("new bunterm is below the tty list, inherits live font size, and prevents double launch", async () => {
13
+ let size=13, finish; const calls=[];
14
+ const {menu,selected}=fixture({controls:{fontSize:()=>size,execute:()=>""},
15
+ create:n=>{calls.push(n);return new Promise(resolve=>{finish=resolve;});}});
16
+ menu.pointer(report(0,0,["left"]));
17
+ const labels=[];
18
+ menu.draw({cellWidth:8,cellHeight:16,CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
19
+ paintFor:c=>c,drawText:text=>labels.push(text),
20
+ canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
21
+ expect(labels.indexOf("+ 新增 bunterm / New bunterm")).toBe(labels.indexOf("bunterm",2)+1);
22
+ size=20;
23
+ menu.input(Buffer.from("\x1b[B".repeat(5)+"\r\r"));
24
+ await Bun.sleep(0);
25
+ expect(calls).toEqual([20]);expect(selected).toEqual([]);
26
+ finish();await Bun.sleep(0);expect(menu.visible).toBe(false);
27
+ });
28
+
29
+ test("failed new bunterm launch keeps the menu usable", async () => {
30
+ const {menu}=fixture({controls:{fontSize:()=>13,execute:()=>""},create:()=>{throw Error("No next tty available");}});
31
+ menu.pointer(report(0,0,["left"]));
32
+ menu.input(Buffer.from("\x1b[B".repeat(5)+"\r"));
33
+ await Bun.sleep(0);expect(menu.visible).toBe(true);
34
+ const labels=[];
35
+ menu.draw({cellWidth:8,cellHeight:16,CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
36
+ paintFor:c=>c,drawText:text=>labels.push(text),
37
+ canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
38
+ expect(labels).toContain("No next tty available");
39
+ menu.input(Buffer.from("\x1b"));expect(menu.visible).toBe(false);
40
+ });
41
+ test("only the top-left four pixels open the menu; open and selection clicks never leak", () => {
42
+ const {menu,selected} = fixture();
43
+ expect(menu.pointer(report(2,1,["left"]))).toBe(false);
44
+ expect(menu.pointer(report(1,2,["left"]))).toBe(false);
45
+ expect(menu.pointer(report(1,1,["right"]))).toBe(false);
46
+ expect(menu.pointer(report(1,1,["left"]))).toBe(true);
47
+ expect(menu.visible).toBe(true);
48
+ expect(menu.pointer(report(1,1,[],["left"]))).toBe(true);
49
+ expect(menu.pointer(report(20,55,["left"]))).toBe(true);
50
+ expect(selected).toEqual([3]);
51
+ expect(menu.visible).toBe(false);
52
+ expect(menu.pointer(report(20,55,[],["left"]))).toBe(true);
53
+ expect(menu.pointer(report(30,60))).toBe(false);
54
+ });
55
+ test("menu consumes application input, supports keyboard selection and dismissal", () => {
56
+ const {menu,selected} = fixture();
57
+ menu.pointer(report(0,0,["left"]));
58
+ expect(menu.input(Buffer.from("shell command"))).toBe(true);
59
+ menu.input(Buffer.from("\x1b[B"));
60
+ menu.input(Buffer.from("\r"));
61
+ expect(selected).toEqual([3]);
62
+ menu.pointer(report(0,0,[],["left"]));
63
+ menu.pointer(report(0,0,["left"]));
64
+ menu.input(Buffer.from("\x1b"));
65
+ expect(menu.visible).toBe(false);
66
+ expect(menu.input(Buffer.from("x"))).toBe(false);
67
+ });
68
+ test("short screens scroll selection; renderer overlays are clipped", () => {
69
+ const {menu,selected} = fixture({height:72});
70
+ menu.pointer(report(0,0,["left"]));
71
+ menu.pointer({...report(10,10), wheel:-1});
72
+ const text=[];
73
+ menu.draw({cellWidth:8, cellHeight:16, CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
74
+ paintFor: c=>c, drawText: t=>text.push(t),
75
+ canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
76
+ expect(text).toContain("bunterm");
77
+ expect(text).not.toContain("initial");
78
+ menu.input(Buffer.from("\r"));
79
+ expect(selected).toEqual([3]);
80
+ });
81
+ test("outside click dismisses without selecting or leaking its release", () => {
82
+ const {menu,selected} = fixture();
83
+ menu.pointer(report(0,0,["left"]));
84
+ menu.pointer(report(0,0,[],["left"]));
85
+ expect(menu.pointer(report(700,300,["left"]))).toBe(true);
86
+ expect(menu.visible).toBe(false);
87
+ expect(menu.pointer(report(700,300,[],["left"]))).toBe(true);
88
+ expect(selected).toEqual([]);
89
+ });
90
+
91
+ test("Escape after releasing the opener does not eat the next application click", () => {
92
+ const {menu} = fixture();
93
+ menu.pointer(report(0,0,["left"]));
94
+ menu.pointer(report(0,0,[],["left"]));
95
+ menu.input(Buffer.from("\x1b"));
96
+ expect(menu.pointer(report(100,100,["left"]))).toBe(false);
97
+ });
98
+
99
+ test("font and clipboard buttons dispatch local commands; UTF-8 command editing survives split reads", async () => {
100
+ const calls=[];
101
+ const {menu}=fixture({controls:{fontSize:()=>13,execute:async line=>{calls.push(line);return "OK";}}});
102
+ menu.pointer(report(0,0,["left"]));
103
+ menu.pointer(report(0,0,[],["left"]));
104
+ for (const [x,y] of [[10,30],[400,30],[10,55],[300,55],[10,80]]) {
105
+ menu.pointer(report(x,y,["left"]));
106
+ menu.pointer(report(x,y,[],["left"]));
107
+ }
108
+ const bytes=Buffer.from("font 16中文");
109
+ menu.input(bytes.subarray(0,9)); menu.input(bytes.subarray(9));
110
+ menu.input(Buffer.from("\x7f\x7f\r")); // remove the two full code points
111
+ await new Promise(resolve=>setTimeout(resolve,0));
112
+ expect(calls).toEqual(["font -","font +","copy","paste","font 16"]);
113
+ expect(menu.visible).toBe(true);
114
+ menu.input(Buffer.from("\x1b"));
115
+ });
116
+
117
+ test("command registry validates font/VT arguments and is extensible", async () => {
118
+ let size=13; const calls=[];
119
+ const c=createCommands({fontSize:()=>size,setFontSize:n=>{if(!Number.isInteger(n)||n<6||n>96)throw Error("size");size=n;},
120
+ copy:()=>calls.push("copy"),paste:()=>calls.push("paste"),switchVT:n=>calls.push(n)});
121
+ expect(await c.execute("font +")).toBe("Font: 14");
122
+ await c.execute("font 18"); expect(size).toBe(18);
123
+ await expect(c.execute("font nope")).rejects.toThrow();
124
+ await expect(c.execute("vt 64")).rejects.toThrow();
125
+ await expect(c.execute("copy extra")).rejects.toThrow();
126
+ await expect(c.execute("unknown")).rejects.toThrow("try help");
127
+ await c.execute("copy"); await c.execute("paste"); await c.execute("vt 3");
128
+ expect(calls).toEqual(["copy","paste",3]);
129
+ c.commands.set("example", args=>args.join(" "));
130
+ expect(await c.execute("example hello world")).toBe("hello world");
131
+ });
132
+
133
+ test("menu labels current VT and renders a real left-aligned command field with centered font value", () => {
134
+ const {menu}=fixture({list:()=>[{number:1,label:"tty1 — initial console",current:true}],
135
+ controls:{fontSize:()=>13,execute:()=>""}});
136
+ menu.pointer(report(0,0,["left"]));
137
+ const labels=[];
138
+ menu.draw({cellWidth:8,cellHeight:16,CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
139
+ paintFor:c=>c,drawText:(text,x,y,cells,color,options)=>labels.push({text,align:options.align}),
140
+ canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
141
+ expect(labels).toContainEqual({text:"tty1 — initial console (current)",align:"left"});
142
+ expect(labels).toContainEqual({text:"Click to type a command…",align:"left"});
143
+ expect(labels).toContainEqual({text:"🔤 字體 Font 13",align:"center"});
144
+ expect(labels).toContainEqual({text:"📋 複製 Copy",align:"left"});
145
+ expect(labels).toContainEqual({text:"📥 貼上 Paste",align:"left"});
146
+ expect(labels.some(x=>x.text===">")).toBe(false);
147
+ });
148
+
149
+ test("font buttons fit equally and preserve the right margin as fonts grow", () => {
150
+ const {menu}=fixture({controls:{fontSize:()=>20,execute:()=>""}});
151
+ menu.pointer(report(0,0,["left"]));
152
+ for(const cellWidth of [8,12,19,58]) {
153
+ const labels=[];
154
+ menu.draw({cellWidth,cellHeight:24,CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
155
+ paintFor:c=>c,drawText:(text,x,y,cells,color,options)=>labels.push({text,x,cells,align:options.align}),
156
+ canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
157
+ const plus=labels.find(x=>x.text==="[+]"),minus=labels.find(x=>x.text==="[-]");
158
+ expect(plus.align).toBe("center");expect(minus.align).toBe("center");
159
+ expect(plus.cells).toBe(minus.cells);
160
+ expect(plus.x+plus.cells*cellWidth).toBe(412);
161
+ expect(minus.x).toBe(8);
162
+ }
163
+ });
164
+
165
+ test('header FPS control toggles without selecting a tty or leaking the click',()=>{
166
+ let enabled=false;
167
+ const {menu,selected}=fixture({controls:{fontSize:()=>13,execute:()=>'',fpsEnabled:()=>enabled,toggleFps:()=>{enabled=!enabled;}}});
168
+ menu.pointer(report(0,0,['left']));
169
+ menu.pointer(report(0,0,[],['left']));
170
+ expect(menu.pointer(report(380,12,['left']))).toBe(true);
171
+ expect(enabled).toBe(true);expect(menu.visible).toBe(true);expect(selected).toEqual([]);
172
+ menu.pointer(report(380,12,[],['left']));
173
+ const labels=[];
174
+ menu.draw({cellWidth:8,cellHeight:16,CanvasKit:{XYWHRect:(...r)=>r,ClipOp:{Intersect:0}},
175
+ paintFor:c=>c,drawText:text=>labels.push(text),canvas:{save(){},restore(){},clipRect(){},drawRect(){}}});
176
+ expect(labels).toContain('[FPS ✓]');
177
+ menu.pointer(report(380,12,['left']));expect(enabled).toBe(false);
178
+ });
@@ -39,8 +39,21 @@ async function invoke(argumentsFor,env={}) {
39
39
  const mock=join(dir,'libc.js');
40
40
  writeFileSync(mock,`import {appendFileSync} from 'node:fs';
41
41
  const record=(name,...args)=>appendFileSync(${JSON.stringify(log)},JSON.stringify([name,...args.map(a=>typeof a==='bigint'?String(a):a)])+'\\n');
42
- export const ptr=x=>x,cString=x=>x,errno=()=>1,strerror=()=> 'mock mount denied',Errno={EINVAL:22,EPERM:1};
43
- export const libc={symbols:{mount:(...a)=>{record('mount',...a);return a[0]?.endsWith('/fail')?-1:0;},umount2:(...a)=>{record('umount',...a);return 0;},chroot:(...a)=>{record('chroot',...a);return 0;}}};`);
42
+ let entered=false;
43
+ const bound=new Map();
44
+ const functions={mount:(...a)=>{record('mount',...a);return a[0]?.endsWith('/fail')?-1:0;},
45
+ umount2:(...a)=>{record('umount',...a);return process.env.MOCK_UNMOUNT_FAIL?-1:0;},
46
+ chroot:(...a)=>{record('chroot',...a);entered=true;return 0;},
47
+ strerror:()=> 'mock mount denied',errno:()=>process.env.MOCK_UNMOUNT_FAIL?5:1};
48
+ export const libc={symbols:new Proxy({}, {get(_target,name){
49
+ if(!bound.has(name)){
50
+ record('resolve',name);
51
+ if(entered||process.env.MOCK_MISSING_SYMBOL===name)throw Error('Cannot load libc symbol '+name);
52
+ bound.set(name,functions[name]);
53
+ }
54
+ return bound.get(name);
55
+ }})};
56
+ export const ptr=x=>x,cString=x=>x,errno=()=>libc.symbols.errno(),strerror=code=>libc.symbols.strerror(code),Errno={EINVAL:22,EPERM:1};`);
44
57
  const source=readFileSync(new URL('../initramfs/bin/chroot',import.meta.url),'utf8')
45
58
  .replace('"../lib/dlopen.js"',JSON.stringify(mock))
46
59
  .replace('"../lib/chroot-options.js"',JSON.stringify(new URL('../initramfs/lib/chroot-options.js',import.meta.url).pathname))
@@ -68,3 +81,28 @@ test('shared tmp replaces default tmpfs and bind failure cleans up even with -k'
68
81
  expect(failure.calls.some(c=>c[0]==='chroot')).toBe(false);
69
82
  expect(failure.calls.filter(c=>c[0]==='umount').map(c=>c[1])).toEqual([failure.root+'/first']);
70
83
  });
84
+
85
+ test('cleanup symbols are resolved before mounting and remain usable after chroot',async()=>{
86
+ const r=await invoke(({host})=>['-n','-b',`${host}:/work`],{MOCK_UNMOUNT_FAIL:'1'});
87
+ expect(r.status).toBe(0);
88
+ expect(r.stderr).toContain('chroot: unmount /work: mock mount denied');
89
+ expect(r.stderr).not.toContain('Cannot load libc symbol');
90
+ const firstMount=r.calls.findIndex(c=>c[0]==='mount');
91
+ for(const name of ['umount2','strerror','errno']){
92
+ const index=r.calls.findIndex(c=>c[0]==='resolve'&&c[1]===name);
93
+ expect(index).toBeGreaterThanOrEqual(0);expect(index).toBeLessThan(firstMount);
94
+ }
95
+ expect(r.calls.filter(c=>c[0]==='umount').map(c=>c[2])).toEqual([0,2]);
96
+ });
97
+
98
+ test('missing cleanup symbol fails before any mount, including keep-mounts',async()=>{
99
+ const r=await invoke(({host})=>['-n','-k','-b',`${host}:/work`],{MOCK_MISSING_SYMBOL:'umount2'});
100
+ expect(r.status).toBe(125);expect(r.stderr).toContain('Cannot load libc symbol umount2');
101
+ expect(r.calls.some(c=>['mount','chroot','umount'].includes(c[0]))).toBe(false);
102
+ });
103
+
104
+ test('no-mount chroot without binds does not require umount2',async()=>{
105
+ const r=await invoke(()=>['-n'],{MOCK_MISSING_SYMBOL:'umount2'});
106
+ expect(r.status).toBe(0);
107
+ expect(r.calls.some(c=>c[0]==='resolve'&&c[1]==='umount2')).toBe(false);
108
+ });
@@ -0,0 +1,46 @@
1
+ import { test, expect } from "bun:test";
2
+ import { parseChvt, switchVt } from "../initramfs/lib/chvt.js";
3
+
4
+ test("chvt accepts VT numbers and help, rejects invalid input before opening devices", async () => {
5
+ expect(parseChvt(["2"])).toEqual({ number: 2 });
6
+ expect(parseChvt(["63"])).toEqual({ number: 63 });
7
+ for (const flag of ["-h", "--help"]) expect(parseChvt([flag]).help).toBe(true);
8
+ for (const args of [[], ["0"], ["64"], ["-1"], ["2x"], ["1", "2"]]) expect(() => parseChvt(args)).toThrow();
9
+ await expect(switchVt(0, {})).rejects.toThrow();
10
+ });
11
+
12
+ test("chvt skips PTY/non-VT consoles, activates only a validated VT and closes descriptors", async () => {
13
+ const events = [];
14
+ await switchVt(2, {
15
+ openConsole([path]) {
16
+ events.push(path);
17
+ if (path === "/dev/tty0") throw Error("missing");
18
+ return { path, close: () => events.push(`close ${path}`) };
19
+ },
20
+ activeConsole(device) { if (device.path === "/dev/tty") throw Error("not a VT"); },
21
+ activateConsole(device, number) { events.push(`activate ${device.path} ${number}`); },
22
+ });
23
+ expect(events).toEqual(["/dev/tty0", "/dev/tty", "close /dev/tty", "/dev/console", "activate /dev/console 2", "close /dev/console"]);
24
+ });
25
+
26
+ test("chvt closes console on failed switch without overriding VT owner or trying another switch", async () => {
27
+ let closed = 0, opened = 0;
28
+ await expect(switchVt(1, {
29
+ openConsole() { opened++; return { close() { closed++; } }; },
30
+ activeConsole() { return 2; },
31
+ activateConsole() { throw Error("switch denied"); },
32
+ })).rejects.toThrow("switch denied");
33
+ expect(opened).toBe(1);
34
+ expect(closed).toBe(1);
35
+ });
36
+
37
+ test("chvt CLI help and invalid arguments work without a Linux console", () => {
38
+ for (const flag of ["-h", "--help"]) {
39
+ const result = Bun.spawnSync([process.execPath, "initramfs/bin/chvt", flag]);
40
+ expect(result.exitCode).toBe(0);
41
+ expect(result.stdout.toString()).toContain("bunterm");
42
+ }
43
+ const invalid = Bun.spawnSync([process.execPath, "initramfs/bin/chvt", "0"]);
44
+ expect(invalid.exitCode).toBe(1);
45
+ expect(invalid.stderr.toString()).toContain("usage:");
46
+ });
@@ -0,0 +1,42 @@
1
+ import { test, expect } from "bun:test";
2
+ import { syncFramebuffer, flush } from "../initramfs/lib/fbdev.js";
3
+ const display = () => ({ address: 0x10000, memoryLength: 4096, path: "/dev/fb0" });
4
+ test("unsupported framebuffer msync is probed once per mapping", () => {
5
+ for (const code of [22, 95]) {
6
+ const fb = display(); let calls = 0;
7
+ const native = { msync(...args) { calls++; expect(args).toEqual([0x10000, 4096, 4]); return -1; }, errno: () => code };
8
+ syncFramebuffer(fb, native); syncFramebuffer(fb, native);
9
+ expect(calls).toBe(1);
10
+ expect(fb.msyncUnsupported).toBe(true);
11
+ }
12
+ });
13
+ test("supported sync continues every frame and retries EINTR", () => {
14
+ const fb = display(); let calls = 0;
15
+ const native = { msync() { return ++calls === 1 ? -1 : 0; }, errno: () => 4 };
16
+ syncFramebuffer(fb, native); syncFramebuffer(fb, native);
17
+ expect(calls).toBe(3);
18
+ expect(fb.msyncUnsupported).toBeUndefined();
19
+ });
20
+ test("real mapping and I/O failures remain visible", () => {
21
+ for (const code of [5, 12, 9]) {
22
+ const fb = display();
23
+ expect(() => syncFramebuffer(fb, {msync: () => -1, errno: () => code, strerror: () => `error ${code}`})).toThrow(`error ${code}`);
24
+ expect(fb.msyncUnsupported).toBeUndefined();
25
+ }
26
+ });
27
+ test("pixel writes continue on unsupported sync and respect framebuffer offsets and stride", () => {
28
+ const fb = {...display(), msyncUnsupported: true, width: 2, height: 2, bytesPerPixel: 1,
29
+ stride: 4, xOffset: 1, yOffset: 1, pixels: new Uint8Array([1,2,0,0,3,4,0,0]), memory: new Uint8Array(16)};
30
+ flush(fb);
31
+ expect([...fb.memory]).toEqual([0,0,0,0,0,1,2,0,0,3,4,0,0,0,0,0]);
32
+ fb.pixels[0] = 9; flush(fb); expect(fb.memory[5]).toBe(9);
33
+ });
34
+ test("contiguous framebuffer writes preserve surrounding virtual rows", () => {
35
+ const fb = {...display(), msyncUnsupported: true, width: 3, height: 2, bytesPerPixel: 4,
36
+ stride: 12, xOffset: 0, yOffset: 1, pixels: Uint8Array.from({length:24},(_,i)=>i+1), memory: new Uint8Array(48).fill(90)};
37
+ flush(fb);
38
+ expect([...fb.memory]).toEqual([...new Uint8Array(12).fill(90),...fb.pixels,...new Uint8Array(12).fill(90)]);
39
+ fb.yOffset=0;fb.pixels.fill(7);flush(fb);
40
+ expect(fb.memory.subarray(0,24)).toEqual(fb.pixels);
41
+ expect(fb.memory.subarray(36)).toEqual(new Uint8Array(12).fill(90));
42
+ });
@@ -23,8 +23,8 @@ const url = `http://127.0.0.1:${server.port}/archive`;
23
23
 
24
24
  test("five exact registry pins share an architecture-independent cache", () => {
25
25
  expect(npmPackages.map(({ name, version }) => `${name}@${version}`)).toEqual([
26
- "buninu-browser@0.2.4", "buninu-land@0.1.4", "@drxiaozhi/casty@1.3.7",
27
- "@drxiaozhi/jspulse@0.2.1", "@drxiaozhi/jspinyin@0.1.1",
26
+ "buninu-browser@0.6.0", "buninu-land@0.3.1", "@drxiaozhi/casty@1.5.1",
27
+ "@drxiaozhi/jspulse@0.2.3", "@drxiaozhi/jspinyin@0.2.0",
28
28
  ]);
29
29
  expect(basename(npmDownloadsDir)).toBe("npm");
30
30
  for (const pkg of npmPackages) {