buninu-linux 0.15.58 → 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 (228) hide show
  1. package/NOTICE.md +46 -46
  2. package/README.md +22 -15
  3. package/README.zh.md +22 -15
  4. package/initramfs/buninu/CHANGELOG.md +47 -0
  5. package/initramfs/buninu/apps/jsgotty/LICENSES/MIT-jsmdcui.txt +22 -0
  6. package/initramfs/buninu/apps/jsgotty/NOTICE +5 -0
  7. package/initramfs/buninu/apps/jsgotty/README.en.md +21 -0
  8. package/initramfs/buninu/apps/jsgotty/README.md +21 -0
  9. package/initramfs/buninu/apps/jsgotty/client-modes.mjs +117 -0
  10. package/initramfs/buninu/apps/jsgotty/client.mjs +100 -10
  11. package/initramfs/buninu/apps/jsgotty/clipboard-powershell.js +18 -0
  12. package/initramfs/buninu/apps/jsgotty/clipboard.js +148 -0
  13. package/initramfs/buninu/apps/jsgotty/gotty.js +13 -0
  14. package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
  15. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  16. package/initramfs/buninu/apps/jsgotty/static/css/index.css +14 -1
  17. package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +107 -8
  18. package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +115 -7
  19. package/initramfs/buninu/apps/jsgotty/terminal-modes.js +332 -0
  20. package/initramfs/buninu/apps/jsgotty/tests/client-exit.test.mjs +45 -0
  21. package/initramfs/buninu/apps/jsgotty/tests/client-modes.test.mjs +105 -0
  22. package/initramfs/buninu/apps/jsgotty/tests/clipboard-browser.test.js +81 -0
  23. package/initramfs/buninu/apps/jsgotty/tests/clipboard.test.js +75 -0
  24. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-collector.cjs +55 -0
  25. package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-debug.js +39 -0
  26. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.js +33 -0
  27. package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.mjs +22 -0
  28. package/initramfs/buninu/apps/jsgotty/tests/nested-cli-pty.test.mjs +116 -0
  29. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes-browser.test.js +144 -0
  30. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.md +44 -0
  31. package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.test.js +166 -0
  32. package/initramfs/buninu/apps/jsgotty/tests/touch-drag-browser.test.js +106 -0
  33. package/initramfs/buninu/apps/jsgotty/tests/wheel-browser.test.js +60 -0
  34. package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +44 -0
  35. package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
  36. package/initramfs/buninu/apps/jsmdcui/src/highlight/go-regexp.js +83 -0
  37. package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +49 -62
  38. package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +8 -5
  39. package/initramfs/buninu/apps/jsmdcui/tests/go-regexp.test.js +98 -0
  40. package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +21 -16
  41. package/initramfs/buninu/apps/jsmdcui/tests/highlight-regions.test.js +73 -0
  42. package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +32 -9
  43. package/initramfs/buninu/apps/jsmdcui/tests/markdown-behavior.mjs +9 -0
  44. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/NORMAL-HTML.md +46 -0
  45. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/README.md +85 -0
  46. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/REPORT.md +328 -0
  47. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/compare.mjs +75 -0
  48. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/go.mod +5 -0
  49. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html-report.json +619 -0
  50. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html.html +9 -0
  51. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/oracle.go +49 -0
  52. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/report.json +2174 -0
  53. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/PowerShell.txt +39 -0
  54. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ada.txt +25 -0
  55. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/apacheconf.txt +49 -0
  56. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/arduino.txt +38 -0
  57. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asciidoc.txt +33 -0
  58. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asm.txt +77 -0
  59. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ats.txt +48 -0
  60. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/awk.txt +28 -0
  61. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/b.txt +27 -0
  62. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/bat.txt +39 -0
  63. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/c.txt +34 -0
  64. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/caddyfile.txt +14 -0
  65. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cake.txt +7 -0
  66. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/clojure.txt +18 -0
  67. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cmake.txt +20 -0
  68. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/coffeescript.txt +32 -0
  69. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/colortest.txt +19 -0
  70. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/conky.txt +12 -0
  71. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cpp.txt +46 -0
  72. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crontab.txt +16 -0
  73. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crystal.txt +28 -0
  74. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csharp.txt +28 -0
  75. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/css.txt +19 -0
  76. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csx.txt +7 -0
  77. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cuda.txt +37 -0
  78. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cython.txt +22 -0
  79. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/d.txt +52 -0
  80. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dart.txt +27 -0
  81. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/default.txt +8 -0
  82. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dockerfile.txt +10 -0
  83. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dot.txt +15 -0
  84. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elixir.txt +23 -0
  85. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elm.txt +20 -0
  86. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erb.txt +32 -0
  87. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erlang.txt +23 -0
  88. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fish.txt +20 -0
  89. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/forth.txt +17 -0
  90. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fortran.txt +40 -0
  91. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/freebsd-kernel.txt +10 -0
  92. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fsharp.txt +27 -0
  93. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gdscript.txt +26 -0
  94. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gemini.txt +11 -0
  95. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-ebuild.txt +28 -0
  96. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-etc-portage.txt +14 -0
  97. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-commit.txt +19 -0
  98. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-config.txt +11 -0
  99. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-rebase-todo.txt +10 -0
  100. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gleam.txt +44 -0
  101. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/glsl.txt +18 -0
  102. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gnuplot.txt +11 -0
  103. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/go.txt +33 -0
  104. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/godoc.txt +9 -0
  105. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/golo.txt +50 -0
  106. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gomod.txt +16 -0
  107. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/graphql.txt +20 -0
  108. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groff.txt +18 -0
  109. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groovy.txt +42 -0
  110. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haml.txt +16 -0
  111. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hare.txt +31 -0
  112. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haskell.txt +26 -0
  113. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hc.txt +28 -0
  114. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html.txt +26 -0
  115. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html4.txt +20 -0
  116. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html5.txt +20 -0
  117. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ini.txt +16 -0
  118. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/inputrc.txt +14 -0
  119. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/java.txt +18 -0
  120. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/javascript.txt +41 -0
  121. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jinja2.txt +13 -0
  122. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/json.txt +21 -0
  123. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jsonnet.txt +28 -0
  124. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/julia.txt +24 -0
  125. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/justfile.txt +26 -0
  126. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/keymap.txt +15 -0
  127. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kickstart.txt +16 -0
  128. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kotlin.txt +30 -0
  129. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kvlang.txt +37 -0
  130. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ledger.txt +13 -0
  131. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lfe.txt +17 -0
  132. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lilypond.txt +15 -0
  133. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lisp.txt +17 -0
  134. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/log.txt +45 -0
  135. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lua.txt +48 -0
  136. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mail.txt +17 -0
  137. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/makefile.txt +25 -0
  138. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/man.txt +12 -0
  139. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/markdown.txt +21 -0
  140. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mc.txt +11 -0
  141. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/meson.txt +29 -0
  142. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/micro.txt +25 -0
  143. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mpdconf.txt +13 -0
  144. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/msbuild.txt +7 -0
  145. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nanorc.txt +16 -0
  146. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nftables.txt +22 -0
  147. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nginx.txt +17 -0
  148. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nim.txt +23 -0
  149. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nix.txt +15 -0
  150. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nu.txt +92 -0
  151. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/objc.txt +36 -0
  152. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ocaml.txt +25 -0
  153. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/octave.txt +35 -0
  154. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/odin.txt +35 -0
  155. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pascal.txt +22 -0
  156. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/patch.txt +13 -0
  157. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/peg.txt +16 -0
  158. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/perl.txt +27 -0
  159. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/php.txt +44 -0
  160. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pkg-config.txt +12 -0
  161. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/po.txt +12 -0
  162. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pony.txt +29 -0
  163. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pov.txt +17 -0
  164. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-action.txt +14 -0
  165. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-config.txt +10 -0
  166. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-filter.txt +12 -0
  167. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/proto.txt +22 -0
  168. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/prql.txt +42 -0
  169. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/puppet.txt +22 -0
  170. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python2.txt +24 -0
  171. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python3.txt +28 -0
  172. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/r.txt +16 -0
  173. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/raku.txt +19 -0
  174. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/reST.txt +18 -0
  175. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/renpy.txt +11 -0
  176. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rpmspec.txt +31 -0
  177. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ruby.txt +34 -0
  178. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rust.txt +27 -0
  179. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sage.txt +24 -0
  180. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scad.txt +23 -0
  181. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scala.txt +16 -0
  182. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sed.txt +12 -0
  183. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sh.txt +20 -0
  184. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sls.txt +15 -0
  185. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/smalltalk.txt +28 -0
  186. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/solidity.txt +26 -0
  187. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sql.txt +35 -0
  188. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/stata.txt +32 -0
  189. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/svelte.txt +10 -0
  190. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/swift.txt +53 -0
  191. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/systemd.txt +15 -0
  192. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tcl.txt +17 -0
  193. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/terraform.txt +28 -0
  194. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tex.txt +15 -0
  195. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/toml.txt +31 -0
  196. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/twig.txt +29 -0
  197. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/typescript.txt +32 -0
  198. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/v.txt +47 -0
  199. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vala.txt +22 -0
  200. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/verilog.txt +37 -0
  201. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vhdl.txt +37 -0
  202. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vi.txt +17 -0
  203. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vue.txt +29 -0
  204. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xml.txt +14 -0
  205. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xresources.txt +14 -0
  206. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yaml.txt +19 -0
  207. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yum.txt +12 -0
  208. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zig.txt +24 -0
  209. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zscript.txt +31 -0
  210. package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zsh.txt +21 -0
  211. package/initramfs/buninu/package.json +1 -1
  212. package/initramfs/lib/bunterm/images.js +5 -1
  213. package/initramfs/lib/bunterm/raw-protocol.js +82 -4
  214. package/initramfs/lib/bunterm/renderer.js +35 -16
  215. package/initramfs/lib/bunterm/terminal.js +11 -1
  216. package/initramfs/lib/fbdev.js +11 -5
  217. package/initramfs/lib/mapped-frame.js +28 -0
  218. package/initramfs/lib/raw-image.js +29 -5
  219. package/package.json +1 -1
  220. package/scripts/arch.js +5 -5
  221. package/scripts/arch.sh +5 -5
  222. package/scripts/npm-packages.md +11 -10
  223. package/test/bunterm-image-bytes.test.js +15 -0
  224. package/test/bunterm-mapped-lifetime.test.js +147 -0
  225. package/test/bunterm-raw-protocol.test.js +20 -0
  226. package/test/bunterm-renderer.test.js +9 -0
  227. package/test/fbdev-sync.test.js +9 -0
  228. package/test/fetch-npm.test.js +2 -2
@@ -1,3 +1,5 @@
1
+ import fs from "node:fs";
2
+ import {ownMapping, retainMapping} from "../mapped-frame.js";
1
3
  // Buninu private extension; not a registered Kitty pixel format.
2
4
  export const BGRA_FORMAT = 0x42475241;
3
5
  export function preferredPixelFormat(display, littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1) {
@@ -14,6 +16,45 @@ export function preferredPixelFormat(display, littleEndian = new Uint8Array(new
14
16
  // Only the native renderer advertises these formats; browser jsgotty is unchanged.
15
17
  export function bgraParser(Base,display) {
16
18
  return class extends Base {
19
+ constructor(...args) {
20
+ super(...args);
21
+ // The parser cache and renderer have independent lifetimes. Replacing a
22
+ // cache entry must not detach a frame still being drawn or handled.
23
+ this.images = new MappedImages(this.images);
24
+ this.mappingReads = [];
25
+ this.disposed = false;
26
+ }
27
+
28
+ dispose() {
29
+ if (this.disposed) return;
30
+ this.disposed = true;
31
+ this.images.clear();
32
+ this.transfers.clear();
33
+ this.pending = Buffer.alloc(0);
34
+ for (const release of this.mappingReads) release?.();
35
+ this.mappingReads = [];
36
+ }
37
+
38
+ _readImageFile(control,payloadText,deleteAfterRead) {
39
+ // Only complete raw temporary frames are immutable producer handoffs.
40
+ // Ordinary files, compressed/partial transfers keep the copying reader.
41
+ const format=Number(control.f);
42
+ if(!deleteAfterRead || ![32,BGRA_FORMAT].includes(format) || control.O || control.S || control.o || typeof Bun.mmap!=="function")
43
+ return super._readImageFile(control,payloadText,deleteAfterRead);
44
+ const path=fs.realpathSync(Buffer.from(payloadText,"base64").toString("utf8"));
45
+ const stat=fs.statSync(path),w=Number(control.s),h=Number(control.v);
46
+ if(!this._isSafeTempGraphicsPath(path)||!stat.isFile()||!Number.isInteger(w)||!Number.isInteger(h)||w<1||h<1||w*h>16777216||stat.size!==w*h*4)
47
+ return super._readImageFile(control,payloadText,deleteAfterRead);
48
+ let bytes;
49
+ try { bytes=Bun.mmap(path,{shared:false}); }
50
+ catch { return super._readImageFile(control,payloadText,deleteAfterRead); }
51
+ // Keep the mapping through parsing, even for queries/errors that do not
52
+ // enter the image cache. Never truncate/reuse producer files.
53
+ this.mappingReads.push(ownMapping(bytes));
54
+ try { fs.unlinkSync(path); } catch {}
55
+ return Buffer.from(bytes.buffer,bytes.byteOffset,bytes.byteLength);
56
+ }
57
+
17
58
  _resolveImageData(control,payload) {
18
59
  const bytes=super._resolveImageData(control,payload);
19
60
  if(Number(control.f)===BGRA_FORMAT){
@@ -24,10 +65,47 @@ export function bgraParser(Base,display) {
24
65
  return bytes;
25
66
  }
26
67
  _parsePacket(packet,cursor) {
27
- const result=super._parsePacket(packet,cursor);
28
- if(result?.kind==='query'&&Number(result.control.f)===BGRA_FORMAT&&result.responseMessage==='OK')
29
- result.responseMessage='OK Buninu1 '+preferredPixelFormat(display);
30
- return result;
68
+ if (this.disposed) return null;
69
+ const previousReads = this.mappingReads;
70
+ this.mappingReads = [];
71
+ try {
72
+ const result=super._parsePacket(packet,cursor);
73
+ const release = retainMapping(result?.image?.data);
74
+ // The consumer uses `using` while awaiting image handling/ACKs. This
75
+ // protects the result even if the parser cache is replaced or closed.
76
+ if (release) result[Symbol.dispose] = release;
77
+ if(result?.kind==='query'&&Number(result.control.f)===BGRA_FORMAT&&result.responseMessage==='OK')
78
+ result.responseMessage='OK Buninu1 '+preferredPixelFormat(display);
79
+ return result;
80
+ } finally {
81
+ for (const release of this.mappingReads) release?.();
82
+ this.mappingReads = previousReads;
83
+ }
31
84
  }
32
85
  };
33
86
  }
87
+
88
+ class MappedImages extends Map {
89
+ constructor(entries) {
90
+ super();
91
+ this.releases = new Map();
92
+ for (const [id, image] of entries) this.set(id, image);
93
+ }
94
+ set(id, image) {
95
+ const release = retainMapping(image?.data);
96
+ this.delete(id);
97
+ super.set(id, image);
98
+ if (release) this.releases.set(id, release);
99
+ return this;
100
+ }
101
+ delete(id) {
102
+ this.releases.get(id)?.();
103
+ this.releases.delete(id);
104
+ return super.delete(id);
105
+ }
106
+ clear() {
107
+ for (const release of this.releases.values()) release();
108
+ this.releases.clear();
109
+ super.clear();
110
+ }
111
+ }
@@ -348,15 +348,24 @@ export class Renderer {
348
348
  }
349
349
  }
350
350
 
351
- drawRow(line, row) {
352
- const { canvas } = this;
353
- canvas.drawRect(this.cellRect(0, row, this.columns, 1), this.paintFor(this.background));
354
- if (!line) return;
355
- for (let column = 0; column < this.columns; column++) {
356
- const cell = line.getCell(column);
357
- if (!cell) break;
358
- this.drawCell(cell, column, row);
351
+ drawRow(line, row, hidden = []) {
352
+ const draw = (first, end) => {
353
+ if(end<=first)return;
354
+ this.canvas.drawRect(this.cellRect(first,row,end-first,1),this.paintFor(this.background));
355
+ if(!line)return;
356
+ for(let column=first;column<end;column++){
357
+ const cell=line.getCell(column);if(!cell)break;
358
+ this.drawCell(cell,column,row);
359
+ }
360
+ };
361
+ // Retain two boundary cells for wide glyphs and italic overhang. Interior
362
+ // pixels will be replaced by the opaque image; omit their background too.
363
+ let column=0;
364
+ for(const [first,end] of hidden.map(([a,b])=>[a+2,b-2]).sort((a,b)=>a[0]-b[0])){
365
+ draw(column,Math.min(this.columns,first));
366
+ column=Math.max(column,end);
359
367
  }
368
+ draw(column,this.columns);
360
369
  }
361
370
 
362
371
  drawCursor(line, x, y) {
@@ -425,28 +434,38 @@ export class Renderer {
425
434
  const buffer = term.buffer.active;
426
435
  const top = buffer.viewportY;
427
436
  const dirty = [];
428
- // Only proven opaque, exact raw copies may hide text work. Partial rows,
429
- // scaling, transparency and unsupported framebuffer layouts remain unchanged.
430
- const coveredRows = new Set();
437
+ // Only proven opaque, exact raw copies may hide text work. Partial-height
438
+ // rows, scaling, transparency and unsupported layouts remain unchanged.
439
+ const coveredRows = new Set(), coveredSpans = new Map();
440
+ const previousSpans = this.rawCoveredSpans ?? new Map();
431
441
  const previouslyCovered = this.rawCoveredRows ?? new Set();
432
442
  for (const {image, rect, source} of images) {
433
443
  const bounds = source || [0, 0, image.width(), image.height()];
434
- if (rect[0] > this.offsetX || rect[2] < this.offsetX + this.columns * this.cellWidth
435
- || !this.screen.canDrawRawImage?.(image, rect, bounds)) continue;
444
+ if (!this.screen.canDrawRawImage?.(image, rect, bounds)) continue;
445
+ const left=Math.max(0,Math.ceil((rect[0]-this.offsetX)/this.cellWidth));
446
+ const right=Math.min(this.columns,Math.floor((rect[2]-this.offsetX)/this.cellWidth));
436
447
  const first = Math.max(0, Math.ceil((rect[1] - this.offsetY) / this.cellHeight));
437
448
  const end = Math.min(this.rows, Math.floor((rect[3] - this.offsetY) / this.cellHeight));
438
- for (let row = first; row < end; row++) coveredRows.add(row);
449
+ for (let row = first; row < end; row++) {
450
+ if(left===0 && right===this.columns)coveredRows.add(row);
451
+ else if(right-left>4){
452
+ const spans=coveredSpans.get(row)??[];spans.push([left,right]);coveredSpans.set(row,spans);
453
+ }
454
+ }
439
455
  }
440
456
  for (let row = 0; row < this.rows; row++) {
441
457
  const line = buffer.getLine(top + row);
442
458
  const hash = coveredRows.has(row) ? this.rowHashes[row] : line ? this.rowHash(line, this.columns) : 0;
443
459
  if (this.dirtyAll || hash !== this.rowHashes[row] || this.forcedRows.has(row)
444
- || coveredRows.has(row) !== previouslyCovered.has(row)) {
460
+ || coveredRows.has(row) !== previouslyCovered.has(row)
461
+ || (coveredSpans.get(row)?.length??0)!==(previousSpans.get(row)?.length??0)
462
+ || coveredSpans.get(row)?.some(([a,b],i)=>a!==previousSpans.get(row)?.[i]?.[0]||b!==previousSpans.get(row)?.[i]?.[1])) {
445
463
  dirty.push(row);
446
464
  this.rowHashes[row] = hash;
447
465
  }
448
466
  }
449
467
  this.rawCoveredRows = coveredRows;
468
+ this.rawCoveredSpans = coveredSpans;
450
469
  this.forcedRows.clear();
451
470
  const localPointer = Boolean(this.screen.saveRegion && this.screen.restoreRegion);
452
471
  const pointerChanged = this.lastPointer?.x !== pointer?.x || this.lastPointer?.y !== pointer?.y;
@@ -477,7 +496,7 @@ export class Renderer {
477
496
  this.dirtyAll = false;
478
497
  dirty.sort((a, b) => a - b);
479
498
  for (const row of dirty) {
480
- if (!coveredRows.has(row)) this.drawRow(buffer.getLine(top + row), row);
499
+ if (!coveredRows.has(row)) this.drawRow(buffer.getLine(top + row), row, coveredSpans.get(row));
481
500
  }
482
501
  if (cursor?.visible && !coveredRows.has(cursor.y) && dirty.includes(cursor.y)) {
483
502
  this.drawCursor(buffer.getLine(top + cursor.y), cursor.x, cursor.y);
@@ -93,6 +93,7 @@ export const createSession = ({
93
93
  });
94
94
 
95
95
  let frameTimer = null;
96
+ let lastPaintTime = -Infinity;
96
97
  let active = true;
97
98
  let blinkOn = true;
98
99
  const cursorHidden = () => Boolean(term._core?.coreService?.isCursorHidden);
@@ -108,6 +109,7 @@ export const createSession = ({
108
109
  if (frameTimer) clearTimeout(frameTimer);
109
110
  frameTimer = null;
110
111
  if (!active) return false;
112
+ lastPaintTime = performance.now();
111
113
  const buffer = term.buffer.active;
112
114
  // cursorY counts from the buffer's base, so a scrolled-back viewport
113
115
  // moves the cursor down the screen and eventually off it.
@@ -129,7 +131,9 @@ export const createSession = ({
129
131
  };
130
132
  const requestFrame = () => {
131
133
  if (!active) return;
132
- frameTimer ??= setTimeout(paint, 16);
134
+ // Limit paint starts, rather than adding 16 ms after every arriving frame.
135
+ // Slow rendering already consumes the interval; keep one pending paint.
136
+ frameTimer ??= setTimeout(paint, Math.max(0, 16 - (performance.now() - lastPaintTime)));
133
137
  };
134
138
  const changed = () => { renderer.invalidate(); requestFrame(); };
135
139
  const selection = createSelection(term, renderer, changed);
@@ -198,8 +202,10 @@ export const createSession = ({
198
202
  let queue = Promise.resolve();
199
203
  const pump = (chunk) => {
200
204
  queue = queue.then(async () => {
205
+ if (closed) return;
201
206
  const { events } = kitty.consume(chunk);
202
207
  for (const event of events) {
208
+ if (closed) return;
203
209
  if (event.kind === "plain") {
204
210
  // A selection is a snapshot of the displayed cells. Drop it before
205
211
  // output scrolls/replaces those cells, rather than copying stale rows.
@@ -210,8 +216,11 @@ export const createSession = ({
210
216
  const buffer = term.buffer.active;
211
217
  const graphic = kitty.parsePacket(event.packet, { row: buffer.cursorY + 1, col: buffer.cursorX + 1 });
212
218
  if (!graphic) continue;
219
+ using frame = graphic[Symbol.dispose] ? graphic : null;
213
220
  const motion = await images.handle(graphic);
221
+ if (closed) return;
214
222
  if (motion) await writeTerminal(term, motion);
223
+ if (closed) return;
215
224
  const ack = kitty.acknowledge(graphic);
216
225
  if (ack) backend.write(Buffer.from(ack, "utf8"));
217
226
  }
@@ -386,6 +395,7 @@ export const createSession = ({
386
395
  keyboardProtocol = null;
387
396
  pointerDevice?.close();
388
397
  pointerDevice = null;
398
+ kitty.dispose();
389
399
  images.delete();
390
400
  renderer.delete();
391
401
  term.dispose();
@@ -283,11 +283,17 @@ export const syncFramebuffer = (display, native = {
283
283
 
284
284
  export const flush = (display) => {
285
285
  const visibleBytes = display.width * display.bytesPerPixel;
286
- for (let y = 0; y < display.height; y++) {
287
- const row = display.pixels.subarray(y * display.stride, y * display.stride + visibleBytes);
288
- const position = (y + display.yOffset) * display.stride
289
- + display.xOffset * display.bytesPerPixel;
290
- display.memory.set(row, position);
286
+ if (display.xOffset === 0 && display.stride === visibleBytes) {
287
+ // Contiguous visible rows need one bulk copy, not one JS call per row.
288
+ display.memory.set(display.pixels.subarray(0, display.height * display.stride),
289
+ display.yOffset * display.stride);
290
+ } else {
291
+ for (let y = 0; y < display.height; y++) {
292
+ const row = display.pixels.subarray(y * display.stride, y * display.stride + visibleBytes);
293
+ const position = (y + display.yOffset) * display.stride
294
+ + display.xOffset * display.bytesPerPixel;
295
+ display.memory.set(row, position);
296
+ }
291
297
  }
292
298
  syncFramebuffer(display);
293
299
  };
@@ -0,0 +1,28 @@
1
+ // Only buffers created by our mmap reader may be detached. Ordinary buffers,
2
+ // pooled Buffer slabs and callers' pixel arrays retain their normal lifetime.
3
+ const mappings = new WeakMap();
4
+
5
+ export function ownMapping(bytes) {
6
+ const buffer = bytes.buffer;
7
+ if (mappings.has(buffer)) throw Error('mapping already owned');
8
+ mappings.set(buffer, {refs: 0});
9
+ return retainMapping(bytes);
10
+ }
11
+
12
+ export function retainMapping(bytes) {
13
+ const buffer = bytes?.buffer;
14
+ const state = buffer && mappings.get(buffer);
15
+ if (!state) return null;
16
+ state.refs++;
17
+ let released = false;
18
+ return () => {
19
+ if (released) return;
20
+ released = true;
21
+ if (--state.refs) return;
22
+ mappings.delete(buffer);
23
+ // Transfer to zero bytes runs Bun's mmap deallocator immediately, rather
24
+ // than batching many full frames into a later GC pause. Older runtimes or
25
+ // non-detachable buffers safely fall back to ordinary GC reclamation.
26
+ try { buffer.transfer?.(0); } catch {}
27
+ };
28
+ }
@@ -1,11 +1,14 @@
1
+ import {retainMapping} from "./mapped-frame.js";
1
2
  // Retain immutable raw pixels until an operation actually needs a Skia Image.
2
3
  // The native image store owns these handles; encoded images remain ordinary Skia images.
3
4
  const rawImages = new WeakMap();
5
+ const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
4
6
  export const rawImageData = image => rawImages.get(image);
5
7
  export const skiaImage = image => rawImages.get(image)?.materialize() ?? image;
6
8
 
7
9
  export function createRawImage(CanvasKit, pixels, width, height, colorType) {
8
10
  let decoded = null, closed = false;
11
+ const release = retainMapping(pixels);
9
12
  const data = {
10
13
  pixels, width, height, colorType,
11
14
  opaque: undefined,
@@ -25,9 +28,13 @@ export function createRawImage(CanvasKit, pixels, width, height, colorType) {
25
28
  delete() {
26
29
  if (closed) return;
27
30
  closed = true;
28
- decoded?.delete(); decoded = null;
29
- data.pixels = pixels = null;
30
- rawImages.delete(image);
31
+ try { decoded?.delete(); }
32
+ finally {
33
+ decoded = null;
34
+ data.pixels = pixels = null;
35
+ rawImages.delete(image);
36
+ release?.();
37
+ }
31
38
  },
32
39
  };
33
40
  rawImages.set(image, data);
@@ -39,8 +46,25 @@ export function rawImageOpaque(data) {
39
46
  if (data.opaque === undefined) {
40
47
  const pixels = data.pixels;
41
48
  data.opaque = true;
42
- for (let i = 3; i < pixels.length; i += 4) {
43
- if (pixels[i] !== 255) { data.opaque = false; break; }
49
+ if (littleEndian && pixels.byteOffset % 4 === 0 && pixels.byteLength % 4 === 0) {
50
+ const words = new Uint32Array(pixels.buffer, pixels.byteOffset, pixels.byteLength / 4);
51
+ let i = 0;
52
+ // All eight alpha bytes must be 255 for their AND to stay 255. Group
53
+ // the common opaque case without skipping any pixel or reading a tail.
54
+ for (; i + 7 < words.length; i += 8) {
55
+ if (((words[i] & words[i + 1] & words[i + 2] & words[i + 3]
56
+ & words[i + 4] & words[i + 5] & words[i + 6] & words[i + 7]) >>> 24) !== 255) {
57
+ data.opaque = false;
58
+ break;
59
+ }
60
+ }
61
+ for (; data.opaque && i < words.length; i++) {
62
+ if ((words[i] >>> 24) !== 255) { data.opaque = false; break; }
63
+ }
64
+ } else {
65
+ for (let i = 3; i < pixels.length; i += 4) {
66
+ if (pixels[i] !== 255) { data.opaque = false; break; }
67
+ }
44
68
  }
45
69
  }
46
70
  return data.opaque;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu-linux",
3
- "version": "0.15.58",
3
+ "version": "0.16.59",
4
4
  "description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
package/scripts/arch.js CHANGED
@@ -16,8 +16,8 @@ export const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
16
16
 
17
17
  const pins = {
18
18
  x86_64: {
19
- linuxVirtSha256: "1eec55004cf7167c914c8839f46691d2bc82767e20466bff51eb45d06f8e90cc",
20
- linuxLtsSha256: "af399fba75cafb2940fc2853eb73ba2289aa463228f33541cd3010334a30992b",
19
+ linuxVirtSha256: "73ad4774ca26c196103a00e2fb8d28ed02de4c08c11fad96e903da03dcb085f4",
20
+ linuxLtsSha256: "ae0a669a08c90ef971996f81f1bcb6f7ca8211ad62624ad8c5b57412c3f1def8",
21
21
  muslSha256: "573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71",
22
22
  stubSha256: "8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9",
23
23
  libstdcppSha256: "14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6",
@@ -32,8 +32,8 @@ const pins = {
32
32
  serialMajor: 4,
33
33
  },
34
34
  aarch64: {
35
- linuxVirtSha256: "b46c51ddf572d85b2ba7b9adfaa64f4c38fc490c93d97a20ffd2418d51284085",
36
- linuxLtsSha256: "65ab581dd6773e2b1b6831dac50142518b2f2083668f164f68ebfcd00d1763c4",
35
+ linuxVirtSha256: "89b68fc32d61a1483ab0d9db78b5a06144ac115fe2ee8102cb4ad7987f46d524",
36
+ linuxLtsSha256: "f6387970517083d5f08abacbf81baf1d70e1baa8fe4c2783469f45bb0751075c",
37
37
  muslSha256: "5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e",
38
38
  stubSha256: "a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38",
39
39
  libstdcppSha256: "2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7",
@@ -68,7 +68,7 @@ export const config = ({
68
68
  // The Alpine repository keeps only the newest build of a package, so a pin
69
69
  // stops downloading when Alpine bumps it; the hashes then fail loudly.
70
70
  const alpineBase = `${mirror}/v3.24/main/${arch}`;
71
- const kernelVersion = "6.18.58";
71
+ const kernelVersion = "6.18.59";
72
72
  const kernelRelease = `${kernelVersion}-0-${flavor}`;
73
73
  const bunVersion = "1.4.2";
74
74
  const downloadsDir = join(rootDir, "downloads", arch);
package/scripts/arch.sh CHANGED
@@ -44,7 +44,7 @@ fi
44
44
  alpine_mirror=${ALPINE_MIRROR:-https://dl-cdn.alpinelinux.org/alpine}
45
45
  alpine_base="$alpine_mirror/v3.24/main/$arch"
46
46
 
47
- kernel_version=6.18.58
47
+ kernel_version=6.18.59
48
48
  kernel_release="$kernel_version-0-$linux_flavor"
49
49
  linux_package="linux-$linux_flavor-$kernel_version-r0.apk"
50
50
  musl_package=musl-1.2.6-r2.apk
@@ -63,8 +63,8 @@ musl_libc="libc.musl-$arch.so.1"
63
63
 
64
64
  case $arch in
65
65
  x86_64)
66
- linux_virt_sha256=1eec55004cf7167c914c8839f46691d2bc82767e20466bff51eb45d06f8e90cc
67
- linux_lts_sha256=af399fba75cafb2940fc2853eb73ba2289aa463228f33541cd3010334a30992b
66
+ linux_virt_sha256=73ad4774ca26c196103a00e2fb8d28ed02de4c08c11fad96e903da03dcb085f4
67
+ linux_lts_sha256=ae0a669a08c90ef971996f81f1bcb6f7ca8211ad62624ad8c5b57412c3f1def8
68
68
  musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
69
69
  stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
70
70
  libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
@@ -82,8 +82,8 @@ case $arch in
82
82
  serial_major=4
83
83
  ;;
84
84
  aarch64)
85
- linux_virt_sha256=b46c51ddf572d85b2ba7b9adfaa64f4c38fc490c93d97a20ffd2418d51284085
86
- linux_lts_sha256=65ab581dd6773e2b1b6831dac50142518b2f2083668f164f68ebfcd00d1763c4
85
+ linux_virt_sha256=89b68fc32d61a1483ab0d9db78b5a06144ac115fe2ee8102cb4ad7987f46d524
86
+ linux_lts_sha256=f6387970517083d5f08abacbf81baf1d70e1baa8fe4c2783469f45bb0751075c
87
87
  musl_sha256=5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e
88
88
  stub_sha256=a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38
89
89
  libstdcpp_sha256=2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7
@@ -1,19 +1,20 @@
1
1
  # npm packages
2
2
  # Latest versions checked on 2026-10-11; SHA-256 pins verified against registry SHA-512 integrity.
3
- # buninu-browser 0.3.0, buninu-land 0.2.0, casty 1.4.1, and jspulse 0.2.3 verified on 2026-10-22.
3
+ # buninu-browser 0.6.0, buninu-land 0.3.1, and casty 1.5.1 verified on 2026-10-29.
4
+ # jspulse 0.2.3 verified on 2026-10-22.
4
5
  # jspinyin 0.2.0 verified on 2026-10-22.
5
6
 
6
- "buninu-browser@0.3.0":
7
- - https://registry.npmjs.org/buninu-browser/-/buninu-browser-0.3.0.tgz
8
- - sha256:4590c523f76ce7c3a079894b80a541b788c95895036d0094c0e98c2acc6d5179
7
+ "buninu-browser@0.6.0":
8
+ - https://registry.npmjs.org/buninu-browser/-/buninu-browser-0.6.0.tgz
9
+ - sha256:81929d903239f615f6265e0b8bf52cc36e34c622486c517bc58d70078c28f72c
9
10
 
10
- "buninu-land@0.2.0":
11
- - https://registry.npmjs.org/buninu-land/-/buninu-land-0.2.0.tgz
12
- - sha256:543098c9f934fe4d723ca65b5a0c8a0bd11a04268228c23c3272073dc4549b56
11
+ "buninu-land@0.3.1":
12
+ - https://registry.npmjs.org/buninu-land/-/buninu-land-0.3.1.tgz
13
+ - sha256:6a2df4b52e12bd20972b195903b72a8a1fdefb62a60391c5f090764283731d22
13
14
 
14
- "@drxiaozhi/casty@1.4.1":
15
- - https://registry.npmjs.org/@drxiaozhi/casty/-/casty-1.4.1.tgz
16
- - sha256:0179c699a82202dc3d84f8736c8d88b29941d87995d6c6b554fd534daa70d280
15
+ "@drxiaozhi/casty@1.5.1":
16
+ - https://registry.npmjs.org/@drxiaozhi/casty/-/casty-1.5.1.tgz
17
+ - sha256:821b9ca5e726afa691c4761d9ec917f862bb1a14592a7e590713c5fc735f71fa
17
18
 
18
19
  "@drxiaozhi/jspulse@0.2.3":
19
20
  - https://registry.npmjs.org/@drxiaozhi/jspulse/-/jspulse-0.2.3.tgz
@@ -53,3 +53,18 @@ test('raw RGBA bytes pass directly to CanvasKit', async () => {
53
53
  expect(received).toBe(image.data);
54
54
  expect(received.equals(pixels)).toBe(true);
55
55
  });
56
+
57
+ import {rawImageOpaque} from '../initramfs/lib/raw-image.js';
58
+ test('raw opacity checks every pixel in aligned and unaligned byte views', () => {
59
+ for (const length of [0, 1, 7, 8, 9, 15, 16, 17, 33]) for (const offset of [0, 1, 4]) {
60
+ const storage = new Uint8Array(length * 4 + offset);
61
+ const pixels = storage.subarray(offset);
62
+ for (let i = 3; i < pixels.length; i += 4) pixels[i] = 255;
63
+ expect(rawImageOpaque({pixels})).toBe(true);
64
+ for (let i = 3; i < pixels.length; i += 4) {
65
+ pixels[i] = 254;
66
+ expect(rawImageOpaque({pixels})).toBe(false);
67
+ pixels[i] = 255;
68
+ }
69
+ }
70
+ });
@@ -0,0 +1,147 @@
1
+ import {test, expect} from 'bun:test';
2
+ import fs from 'node:fs';
3
+ import {tmpdir} from 'node:os';
4
+ import {join} from 'node:path';
5
+ import {createRequire} from 'node:module';
6
+ import {bgraParser, BGRA_FORMAT} from '../initramfs/lib/bunterm/raw-protocol.js';
7
+ import {ownMapping, retainMapping} from '../initramfs/lib/mapped-frame.js';
8
+ import {createRawImage, rawImageData, skiaImage} from '../initramfs/lib/raw-image.js';
9
+ import {ImageStore} from '../initramfs/lib/bunterm/images.js';
10
+ const {KittyGraphicsParser} = createRequire(import.meta.url)('../initramfs/buninu/apps/jsgotty/gotty.js');
11
+ const CK = {ColorType:{RGBA_8888:1,BGRA_8888:2}};
12
+ function fixture() {
13
+ const dir=fs.mkdtempSync(join(tmpdir(),'tty-graphics-protocol-lifetime-'));
14
+ const parser=new (bgraParser(KittyGraphicsParser))({binaryData:true});
15
+ const term={buffer:{active:{type:'alternate',baseY:0,cursorY:0,cursorX:0,viewportY:0}}};
16
+ const store=new ImageStore({CanvasKit:CK,term,renderer:{cellWidth:1,cellHeight:1,offsetX:0,offsetY:0,rows:10,invalidateRows(){}}});
17
+ const parse=(control,payload='')=>parser.parsePacket(Buffer.from(control+';'+payload),{row:1,col:1});
18
+ let serial=0;
19
+ return {parser,store,parse,frame(id=1,format=BGRA_FORMAT){
20
+ const path=join(dir,'frame'+serial++);fs.writeFileSync(path,Buffer.from([1,2,3,255]));
21
+ return parse(`a=T,t=t,f=${format},s=1,v=1,i=${id},C=1`,Buffer.from(path).toString('base64'));
22
+ },close(){parser.dispose();store.delete();fs.rmSync(dir,{recursive:true,force:true});}};
23
+ }
24
+
25
+ test('replacement protects parser results and renderer until their independent owners release',async()=>{
26
+ const f=fixture();
27
+ try {
28
+ using first=f.frame();const bytes=first.image.data;
29
+ await f.store.handle(first);
30
+ using second=f.frame();
31
+ expect(bytes.byteLength).toBe(4); // parser replaced, renderer/result still own old frame
32
+ await f.store.handle(second);
33
+ expect(bytes.byteLength).toBe(4); // result is still borrowed
34
+ first[Symbol.dispose]();first[Symbol.dispose]();
35
+ expect(bytes.byteLength).toBe(0);
36
+ const next=second.image.data;
37
+ f.parser.dispose();second[Symbol.dispose]();
38
+ expect(next.byteLength).toBe(4); // renderer outlives parser
39
+ f.store.delete();expect(next.byteLength).toBe(0);
40
+ } finally {f.close();}
41
+ });
42
+
43
+ test('multiple placements and lowercase deletion retain pixels for re-placement',async()=>{
44
+ const f=fixture();
45
+ try {
46
+ using first=f.frame(1,32);const bytes=first.image.data;
47
+ await f.store.handle(first);first[Symbol.dispose]();
48
+ using second=f.parse('a=p,i=1,p=two,C=1');await f.store.handle(second);second[Symbol.dispose]();
49
+ expect(f.store.visible()).toHaveLength(2);
50
+ await f.store.handle(f.parse('a=d,d=i,i=1,p=two'));
51
+ expect(f.store.visible()).toHaveLength(1);expect(bytes.byteLength).toBe(4);
52
+ await f.store.handle(f.parse('a=d,d=a'));
53
+ expect(f.store.visible()).toHaveLength(0);expect(bytes.byteLength).toBe(4);
54
+ using again=f.parse('a=p,i=1,C=1');await f.store.handle(again);again[Symbol.dispose]();
55
+ expect(rawImageData(f.store.visible()[0].image).pixels[0]).toBe(1);
56
+ await f.store.handle(f.parse('a=d,d=A'));
57
+ expect(bytes.byteLength).toBe(0);
58
+ } finally {f.close();}
59
+ });
60
+
61
+ test('queued frames survive replacement and parser shutdown before consumer handles them',async()=>{
62
+ const f=fixture();
63
+ try {
64
+ using first=f.frame();using second=f.frame();f.parser.dispose();
65
+ await f.store.handle(first);first[Symbol.dispose]();
66
+ expect(first.image.data.byteLength).toBe(4);
67
+ await f.store.handle(second);expect(first.image.data.byteLength).toBe(0);
68
+ second[Symbol.dispose]();f.store.delete();expect(second.image.data.byteLength).toBe(0);
69
+ expect(f.frame()).toBeNull();
70
+ } finally {f.close();}
71
+ });
72
+
73
+ test('closing while decode awaits cannot repopulate the image store',async()=>{
74
+ const f=fixture();
75
+ try {
76
+ using first=f.frame();const handling=f.store.handle(first);
77
+ f.store.delete();f.parser.dispose();await handling;
78
+ expect(f.store.images.size).toBe(0);expect(f.store.placements.size).toBe(0);
79
+ first[Symbol.dispose]();expect(first.image.data.byteLength).toBe(0);
80
+ } finally {f.close();}
81
+ });
82
+
83
+ test('using releases a borrowed frame when processing throws; ordinary buffers never detach',()=>{
84
+ const f=fixture();let bytes;
85
+ try {
86
+ expect(()=>{using first=f.frame();bytes=first.image.data;f.parser.dispose();throw Error('test');}).toThrow('test');
87
+ expect(bytes.byteLength).toBe(0);
88
+ const ordinary=Buffer.alloc(4);const image=createRawImage(CK,ordinary,1,1,1);
89
+ image.delete();expect(ordinary.byteLength).toBe(4);
90
+ } finally {f.close();}
91
+ });
92
+
93
+ test('failed/unsupported transfer falls back to GC without breaking cleanup',()=>{
94
+ for(const transfer of [undefined,()=>{throw Error('not detachable');}]){
95
+ const bytes=new Uint8Array(4);Object.defineProperty(bytes.buffer,'transfer',{value:transfer});
96
+ const release=ownMapping(bytes),other=retainMapping(bytes);
97
+ release();expect(bytes.byteLength).toBe(4);
98
+ expect(()=>{other();other();release();}).not.toThrow();
99
+ expect(bytes.byteLength).toBe(4);
100
+ }
101
+ });
102
+
103
+ test('query/error mappings are released even though they never enter the image cache',()=>{
104
+ const f=fixture();const mapped=[];
105
+ const read=f.parser._readImageFile.bind(f.parser);
106
+ f.parser._readImageFile=(...args)=>{const bytes=read(...args);mapped.push(bytes);return bytes;};
107
+ try {
108
+ // A query validates its file then drops its data. Exercise the same packet
109
+ // cleanup on an exception after the read, before the cache can retain it.
110
+ for(const fail of [false,true]){
111
+ const path=join(tmpdir(),'tty-graphics-protocol-query-'+process.pid);fs.writeFileSync(path,Buffer.from([1,2,3,255]));
112
+ const resolve=f.parser._resolveImageData.bind(f.parser);
113
+ if(fail)f.parser._resolveImageData=(...args)=>{resolve(...args);throw Error('validation failed');};
114
+ const result=f.parse(`a=q,t=t,f=${BGRA_FORMAT},s=1,v=1,i=7`,Buffer.from(path).toString('base64'));
115
+ expect(result.responseMessage).toStartWith(fail?'ERROR:':'OK');
116
+ expect(mapped.at(-1).byteLength).toBe(0);
117
+ }
118
+ expect(f.parser.images.size).toBe(0);
119
+ } finally {f.close();}
120
+ });
121
+
122
+ test('materialized Skia images and mapped bytes both release even if Skia cleanup throws',()=>{
123
+ const bytes=new Uint8Array(4),owner=ownMapping(bytes);
124
+ const image=createRawImage({...CK,AlphaType:{Unpremul:1},ColorSpace:{SRGB:1},MakeImage(){return {delete(){throw Error('Skia cleanup');}};}},bytes,1,1,1);
125
+ owner();skiaImage(image);
126
+ expect(()=>image.delete()).toThrow('Skia cleanup');expect(bytes.byteLength).toBe(0);
127
+ expect(()=>image.delete()).not.toThrow();
128
+ });
129
+
130
+ test('repeated mapped replacements unmap promptly without waiting for a GC',async()=>{
131
+ const f=fixture();let previous;
132
+ try {
133
+ for(let i=0;i<200;i++){
134
+ using frame=f.frame();await f.store.handle(frame);
135
+ if(previous)expect(previous.byteLength).toBe(0);
136
+ previous=frame.image.data;
137
+ expect(previous[3]).toBe(255);
138
+ }
139
+ f.parser.dispose();f.store.delete();expect(previous.byteLength).toBe(0);
140
+ if(process.platform==='linux'){
141
+ const maps=fs.readFileSync('/proc/self/maps','utf8');
142
+ // Files were unlinked at read time; lingering mappings would still have
143
+ // their original name followed by '(deleted)' here.
144
+ expect(maps.split('\n').filter(line=>line.includes('tty-graphics-protocol-lifetime-'))).toHaveLength(0);
145
+ }
146
+ } finally {f.close();}
147
+ });
@@ -17,3 +17,23 @@ test('native preference follows framebuffer bitfields; browser parser remains un
17
17
  const bad=fb();bad.fields.red.msbRight=1;
18
18
  expect(preferredPixelFormat(bad)).toBe('png');expect(preferredPixelFormat(fb(),false)).toBe('png');
19
19
  });
20
+
21
+ import fs from 'node:fs';
22
+ import {tmpdir} from 'node:os';
23
+ import {join} from 'node:path';
24
+ test('raw temporary mappings survive unlink; regular and partial files retain copying behavior',()=>{
25
+ const dir=fs.mkdtempSync(join(tmpdir(),'tty-graphics-protocol-mmap-'));
26
+ const parser=new (bgraParser(KittyGraphicsParser,fb()))({binaryData:true});
27
+ const bytes=Buffer.from([1,2,3,255,4,5,6,255]);
28
+ try{
29
+ for(const format of [32,BGRA_FORMAT]){
30
+ const path=join(dir,'frame');fs.writeFileSync(path,bytes);
31
+ const got=parser._readImageFile({f:String(format),s:'2',v:'1'},Buffer.from(path).toString('base64'),true);
32
+ expect(fs.existsSync(path)).toBe(false);expect(got).toEqual(bytes);
33
+ }
34
+ const path=join(dir,'regular');fs.writeFileSync(path,bytes);
35
+ const payload=Buffer.from(path).toString('base64'),control={f:String(BGRA_FORMAT),s:'2',v:'1'};
36
+ expect(parser._readImageFile(control,payload,false)).toEqual(bytes);expect(fs.existsSync(path)).toBe(true);
37
+ expect(parser._readImageFile({...control,O:'4',S:'4'},payload,true)).toEqual(bytes.subarray(4));
38
+ }finally{parser.dispose();fs.rmSync(dir,{recursive:true,force:true});}
39
+ });