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.
- package/LICENSES/linux-firmware/LICENCE.nvidia +131 -0
- package/LICENSES/linux-firmware/LICENSE.amdgpu +51 -0
- package/LICENSES/linux-firmware/LICENSE.amdisp +31 -0
- package/LICENSES/linux-firmware/LICENSE.i915 +39 -0
- package/LICENSES/linux-firmware/LICENSE.radeon +51 -0
- package/LICENSES/linux-firmware/LICENSE.xe +39 -0
- package/LICENSES/linux-firmware/WHENCE +10280 -0
- package/NOTICE.md +64 -46
- package/README.md +378 -87
- package/README.zh.md +2041 -0
- package/build-image.sh +10 -1
- package/build-uki.sh +3 -1
- package/fetch-alpine.sh +14 -0
- package/graphics.md +31 -0
- package/index.js +28 -11
- package/initramfs/bin/buninu-browser +4 -1
- package/initramfs/bin/buninu-land +1 -1
- package/initramfs/bin/bunterm +3 -3
- package/initramfs/bin/chroot +13 -0
- package/initramfs/bin/chvt +11 -0
- package/initramfs/bin/mount +4 -0
- package/initramfs/bin/umount +6 -0
- package/initramfs/bin/xdg-open +33 -0
- package/initramfs/buninu/CHANGELOG.md +66 -0
- package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +4 -0
- package/initramfs/buninu/apps/bunmsh/package.json +1 -1
- package/initramfs/buninu/apps/bunmsh/src/main.js +1 -1
- package/initramfs/buninu/apps/jsgotty/LICENSES/MIT-jsmdcui.txt +22 -0
- package/initramfs/buninu/apps/jsgotty/NOTICE +5 -0
- package/initramfs/buninu/apps/jsgotty/README.en.md +61 -0
- package/initramfs/buninu/apps/jsgotty/README.md +61 -0
- package/initramfs/buninu/apps/jsgotty/client-modes.mjs +117 -0
- package/initramfs/buninu/apps/jsgotty/client.mjs +100 -10
- package/initramfs/buninu/apps/jsgotty/clipboard-powershell.js +18 -0
- package/initramfs/buninu/apps/jsgotty/clipboard.js +148 -0
- package/initramfs/buninu/apps/jsgotty/gotty.js +13 -0
- package/initramfs/buninu/apps/jsgotty/package-lock.json +2 -2
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/apps/jsgotty/static/css/index.css +14 -1
- package/initramfs/buninu/apps/jsgotty/static/index.html +1 -0
- package/initramfs/buninu/apps/jsgotty/static/js/gotty.js +187 -23
- package/initramfs/buninu/apps/jsgotty/static/js/kitty-keyboard.js +193 -0
- package/initramfs/buninu/apps/jsgotty/static/js/kitty.js +115 -7
- package/initramfs/buninu/apps/jsgotty/terminal-modes.js +332 -0
- package/initramfs/buninu/apps/jsgotty/tests/android-alt.test.js +115 -0
- package/initramfs/buninu/apps/jsgotty/tests/arrow-modifiers.test.js +80 -0
- package/initramfs/buninu/apps/jsgotty/tests/client-exit.test.mjs +45 -0
- package/initramfs/buninu/apps/jsgotty/tests/client-modes.test.mjs +105 -0
- package/initramfs/buninu/apps/jsgotty/tests/clipboard-browser.test.js +81 -0
- package/initramfs/buninu/apps/jsgotty/tests/clipboard.test.js +75 -0
- package/initramfs/buninu/apps/jsgotty/tests/kitty-keyboard-browser.test.js +53 -0
- package/initramfs/buninu/apps/jsgotty/tests/kitty-keyboard.test.js +102 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/README.md +20 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/keyboard-collector.cjs +55 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/keyboard-debug.js +74 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/kitty-keyboard.js +80 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-collector.cjs +55 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual/zoom-debug.js +39 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.js +33 -0
- package/initramfs/buninu/apps/jsgotty/tests/manual-touch-diagnostics.mjs +22 -0
- package/initramfs/buninu/apps/jsgotty/tests/nested-cli-pty.test.mjs +116 -0
- package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes-browser.test.js +144 -0
- package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.md +44 -0
- package/initramfs/buninu/apps/jsgotty/tests/reconnect-modes.test.js +166 -0
- package/initramfs/buninu/apps/jsgotty/tests/touch-drag-browser.test.js +106 -0
- package/initramfs/buninu/apps/jsgotty/tests/wheel-browser.test.js +60 -0
- package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +44 -0
- package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
- package/initramfs/buninu/apps/jsmdcui/src/highlight/go-regexp.js +83 -0
- package/initramfs/buninu/apps/jsmdcui/src/highlight/highlighter.js +49 -62
- package/initramfs/buninu/apps/jsmdcui/src/highlight/parser.js +8 -5
- package/initramfs/buninu/apps/jsmdcui/tests/go-regexp.test.js +98 -0
- package/initramfs/buninu/apps/jsmdcui/tests/heading-ids.test.js +21 -16
- package/initramfs/buninu/apps/jsmdcui/tests/highlight-regions.test.js +73 -0
- package/initramfs/buninu/apps/jsmdcui/tests/id-collision.test.js +32 -9
- package/initramfs/buninu/apps/jsmdcui/tests/markdown-behavior.mjs +9 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/NORMAL-HTML.md +46 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/README.md +85 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/REPORT.md +328 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/compare.mjs +75 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/go.mod +5 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html-report.json +619 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/normal-html.html +9 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/oracle.go +49 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/report.json +2174 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/PowerShell.txt +39 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ada.txt +25 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/apacheconf.txt +49 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/arduino.txt +38 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asciidoc.txt +33 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/asm.txt +77 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ats.txt +48 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/awk.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/b.txt +27 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/bat.txt +39 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/c.txt +34 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/caddyfile.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cake.txt +7 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/clojure.txt +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cmake.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/coffeescript.txt +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/colortest.txt +19 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/conky.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cpp.txt +46 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crontab.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/crystal.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csharp.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/css.txt +19 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/csx.txt +7 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cuda.txt +37 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/cython.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/d.txt +52 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dart.txt +27 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/default.txt +8 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dockerfile.txt +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/dot.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elixir.txt +23 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/elm.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erb.txt +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/erlang.txt +23 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fish.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/forth.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fortran.txt +40 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/freebsd-kernel.txt +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/fsharp.txt +27 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gdscript.txt +26 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gemini.txt +11 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-ebuild.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gentoo-etc-portage.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-commit.txt +19 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-config.txt +11 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/git-rebase-todo.txt +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gleam.txt +44 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/glsl.txt +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gnuplot.txt +11 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/go.txt +33 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/godoc.txt +9 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/golo.txt +50 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/gomod.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/graphql.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groff.txt +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/groovy.txt +42 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haml.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hare.txt +31 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/haskell.txt +26 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/hc.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html.txt +26 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html4.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/html5.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ini.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/inputrc.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/java.txt +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/javascript.txt +41 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jinja2.txt +13 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/json.txt +21 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/jsonnet.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/julia.txt +24 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/justfile.txt +26 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/keymap.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kickstart.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kotlin.txt +30 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/kvlang.txt +37 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ledger.txt +13 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lfe.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lilypond.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lisp.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/log.txt +45 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/lua.txt +48 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mail.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/makefile.txt +25 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/man.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/markdown.txt +21 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mc.txt +11 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/meson.txt +29 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/micro.txt +25 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/mpdconf.txt +13 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/msbuild.txt +7 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nanorc.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nftables.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nginx.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nim.txt +23 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nix.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/nu.txt +92 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/objc.txt +36 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ocaml.txt +25 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/octave.txt +35 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/odin.txt +35 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pascal.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/patch.txt +13 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/peg.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/perl.txt +27 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/php.txt +44 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pkg-config.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/po.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pony.txt +29 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/pov.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-action.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-config.txt +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/privoxy-filter.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/proto.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/prql.txt +42 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/puppet.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python2.txt +24 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/python3.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/r.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/raku.txt +19 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/reST.txt +18 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/renpy.txt +11 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rpmspec.txt +31 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/ruby.txt +34 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/rust.txt +27 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sage.txt +24 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scad.txt +23 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/scala.txt +16 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sed.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sh.txt +20 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sls.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/smalltalk.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/solidity.txt +26 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/sql.txt +35 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/stata.txt +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/svelte.txt +10 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/swift.txt +53 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/systemd.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tcl.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/terraform.txt +28 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/tex.txt +15 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/toml.txt +31 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/twig.txt +29 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/typescript.txt +32 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/v.txt +47 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vala.txt +22 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/verilog.txt +37 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vhdl.txt +37 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vi.txt +17 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/vue.txt +29 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xml.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/xresources.txt +14 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yaml.txt +19 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/yum.txt +12 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zig.txt +24 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zscript.txt +31 -0
- package/initramfs/buninu/apps/jsmdcui/tests/syntax-parity/samples/zsh.txt +21 -0
- package/initramfs/buninu/package.json +1 -1
- package/initramfs/init.js +19 -3
- package/initramfs/lib/bunterm/clipboard.js +14 -0
- package/initramfs/lib/bunterm/commands.js +37 -0
- package/initramfs/lib/bunterm/fps.js +38 -0
- package/initramfs/lib/bunterm/images.js +14 -6
- package/initramfs/lib/bunterm/input.js +58 -15
- package/initramfs/lib/bunterm/keyboard.js +62 -0
- package/initramfs/lib/bunterm/keycodes.js +104 -0
- package/initramfs/lib/bunterm/kitty-keyboard.cjs +195 -0
- package/initramfs/lib/bunterm/raw-protocol.js +111 -0
- package/initramfs/lib/bunterm/renderer.js +116 -28
- package/initramfs/lib/bunterm/selection.js +60 -0
- package/initramfs/lib/bunterm/terminal.js +157 -12
- package/initramfs/lib/bunterm/vt-menu.js +181 -0
- package/initramfs/lib/bunterm/vt.js +27 -1
- package/initramfs/lib/canvas.js +99 -0
- package/initramfs/lib/chvt.js +31 -0
- package/initramfs/lib/fbdev.js +32 -8
- package/initramfs/lib/gpu.js +77 -0
- package/initramfs/lib/mapped-frame.js +28 -0
- package/initramfs/lib/raw-image.js +71 -0
- package/initramfs/lib/xdg-open.js +42 -0
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +85 -11
- package/initramfs/usr/share/doc/buninu-linux/chvt.md +34 -0
- package/initramfs/usr/share/doc/buninu-linux/xdg-open.md +43 -0
- package/package.json +1 -1
- package/scripts/arch.js +7 -5
- package/scripts/arch.sh +5 -5
- package/scripts/build.js +16 -3
- package/scripts/fetch-gpu.js +105 -0
- package/scripts/fetch.js +11 -1
- package/scripts/gpu-firmware.json +22 -0
- package/scripts/modules.js +4 -1
- package/scripts/npm-packages.md +18 -17
- package/test/bunterm-fps.test.js +17 -0
- package/test/bunterm-image-bytes.test.js +15 -0
- package/test/bunterm-keyboard.test.js +116 -0
- package/test/bunterm-mapped-lifetime.test.js +147 -0
- package/test/bunterm-next-vt.test.js +9 -0
- package/test/bunterm-raw-protocol.test.js +39 -0
- package/test/bunterm-renderer.test.js +167 -0
- package/test/bunterm-selection.test.js +38 -0
- package/test/bunterm-vt-menu.test.js +178 -0
- package/test/chroot-bind.test.js +40 -2
- package/test/chvt.test.js +46 -0
- package/test/fbdev-sync.test.js +42 -0
- package/test/fetch-npm.test.js +2 -2
- package/test/gpu.test.js +155 -0
- package/test/image-size.test.js +17 -0
- package/test/manual-bunterm-image-paths.js +31 -0
- package/test/mount-libc-lifetime.test.js +50 -0
- package/test/no-gpu.test.js +43 -0
- package/test/readme-cli.test.js +29 -0
- package/test/stage-npm.test.js +11 -4
- package/test/xdg-open.test.js +55 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {bgraParser} from './raw-protocol.js';
|
|
1
2
|
// /lib/bunterm/terminal.js — a graphical terminal on the Linux framebuffer.
|
|
2
3
|
//
|
|
3
4
|
// Bun.spawn({ terminal }) ──── PTY output ────▶ KittyGraphicsParser
|
|
@@ -13,17 +14,22 @@
|
|
|
13
14
|
// keyboard are Buninu's own.
|
|
14
15
|
|
|
15
16
|
import { createRequire } from "node:module";
|
|
16
|
-
import { runGraphics } from "../fbdev.js";
|
|
17
|
+
import { runGraphics, activeConsole } from "../fbdev.js";
|
|
17
18
|
import { loadCanvasKit, openCanvas, loadFontSet } from "../canvas.js";
|
|
18
19
|
import { Terminal } from "../xterm/xterm-headless.mjs";
|
|
19
20
|
import { UnicodeGraphemesAddon } from "../xterm/addon-unicode-graphemes.mjs";
|
|
20
21
|
import { Renderer, defaultTheme } from "./renderer.js";
|
|
21
22
|
import { ImageStore } from "./images.js";
|
|
22
23
|
import { openKeyboard, translateKeys } from "./input.js";
|
|
23
|
-
import { claimVirtualConsole } from "./vt.js";
|
|
24
|
+
import { claimVirtualConsole, listVirtualTerminals, nextVirtualTerminal } from "./vt.js";
|
|
25
|
+
import { createFpsMeter } from "./fps.js";
|
|
26
|
+
import { createVtMenu } from "./vt-menu.js";
|
|
24
27
|
import { attachGeometry } from "./geometry.js";
|
|
25
28
|
import { terminalMouseEvent } from "./pointer-report.js";
|
|
26
|
-
import { attachClipboard } from "./clipboard.js";
|
|
29
|
+
import { attachClipboard, readClipboard, writeClipboard, pasteText } from "./clipboard.js";
|
|
30
|
+
import { createSelection } from "./selection.js";
|
|
31
|
+
import { createCommands } from "./commands.js";
|
|
32
|
+
import { attachKeyboard as createKeyboard } from "./keyboard.js";
|
|
27
33
|
|
|
28
34
|
const require = createRequire(import.meta.url);
|
|
29
35
|
|
|
@@ -57,6 +63,7 @@ export const createSession = ({
|
|
|
57
63
|
mouse = true,
|
|
58
64
|
clipboard = true,
|
|
59
65
|
env = {},
|
|
66
|
+
vtMenu = null,
|
|
60
67
|
onError = (error) => console.error(`bunterm: ${error?.stack ?? error}`),
|
|
61
68
|
}) => {
|
|
62
69
|
const { BunPtyBackend, KittyGraphicsParser } = loadJsgotty();
|
|
@@ -70,7 +77,7 @@ export const createSession = ({
|
|
|
70
77
|
term.loadAddon(new UnicodeGraphemesAddon());
|
|
71
78
|
term.unicode.activeVersion = "15-graphemes";
|
|
72
79
|
const images = new ImageStore({ CanvasKit, term, renderer });
|
|
73
|
-
const kitty = new KittyGraphicsParser({ binaryData: true });
|
|
80
|
+
const kitty = new (bgraParser(KittyGraphicsParser,screen.display))({ binaryData: true });
|
|
74
81
|
|
|
75
82
|
const backend = new BunPtyBackend({
|
|
76
83
|
command,
|
|
@@ -86,42 +93,99 @@ export const createSession = ({
|
|
|
86
93
|
});
|
|
87
94
|
|
|
88
95
|
let frameTimer = null;
|
|
96
|
+
let lastPaintTime = -Infinity;
|
|
89
97
|
let active = true;
|
|
90
98
|
let blinkOn = true;
|
|
91
99
|
const cursorHidden = () => Boolean(term._core?.coreService?.isCursorHidden);
|
|
92
100
|
// Only set when a pointer is attached; without one the renderer is called
|
|
93
101
|
// exactly as before.
|
|
94
102
|
let pointer = null;
|
|
103
|
+
let menu = null;
|
|
104
|
+
const fps = createFpsMeter();
|
|
105
|
+
let fpsTimer = null;
|
|
106
|
+
let keyboardProtocol = null;
|
|
107
|
+
let consoleKeyboard = null;
|
|
95
108
|
const paint = () => {
|
|
96
109
|
if (frameTimer) clearTimeout(frameTimer);
|
|
97
110
|
frameTimer = null;
|
|
98
111
|
if (!active) return false;
|
|
112
|
+
lastPaintTime = performance.now();
|
|
99
113
|
const buffer = term.buffer.active;
|
|
100
114
|
// cursorY counts from the buffer's base, so a scrolled-back viewport
|
|
101
115
|
// moves the cursor down the screen and eventually off it.
|
|
102
116
|
const row = buffer.baseY + buffer.cursorY - buffer.viewportY;
|
|
103
|
-
|
|
117
|
+
if (menu?.visible || selection.active) renderer.invalidate();
|
|
118
|
+
const visibleImages = images.visible();
|
|
119
|
+
const rendered = renderer.render(term, {
|
|
104
120
|
cursor: {
|
|
105
121
|
x: buffer.cursorX,
|
|
106
122
|
y: row,
|
|
107
123
|
visible: blinkOn && !cursorHidden() && row >= 0 && row < renderer.rows,
|
|
108
124
|
},
|
|
109
|
-
images:
|
|
125
|
+
images: visibleImages,
|
|
110
126
|
pointer,
|
|
127
|
+
overlay: () => { selection.draw(); fps.draw(renderer); if (menu?.visible) menu.draw(renderer); },
|
|
111
128
|
});
|
|
129
|
+
if (rendered && fps.enabled) fps.presented(visibleImages);
|
|
130
|
+
return rendered;
|
|
112
131
|
};
|
|
113
132
|
const requestFrame = () => {
|
|
114
133
|
if (!active) return;
|
|
115
|
-
|
|
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)));
|
|
116
137
|
};
|
|
138
|
+
const changed = () => { renderer.invalidate(); requestFrame(); };
|
|
139
|
+
const selection = createSelection(term, renderer, changed);
|
|
140
|
+
const setFontSize = size => {
|
|
141
|
+
renderer.setFontSize(size);
|
|
142
|
+
selection.clear();
|
|
143
|
+
term.resize(renderer.columns, renderer.rows);
|
|
144
|
+
backend.resize(renderer.columns, renderer.rows);
|
|
145
|
+
changed();
|
|
146
|
+
};
|
|
147
|
+
const copy = async () => {
|
|
148
|
+
if (!clipboard) throw new Error("Clipboard disabled (--no-clipboard)");
|
|
149
|
+
if (!selection.active) throw new Error("Drag to select text first (application mouse reporting must be off)");
|
|
150
|
+
await writeClipboard(selection.text());
|
|
151
|
+
};
|
|
152
|
+
const paste = async () => {
|
|
153
|
+
if (!clipboard) throw new Error("Clipboard disabled (--no-clipboard)");
|
|
154
|
+
const text = await readClipboard();
|
|
155
|
+
if (closed) return;
|
|
156
|
+
pasteText(term, text, value => backend.write(Buffer.from(value, "utf8")));
|
|
157
|
+
menu?.close();
|
|
158
|
+
term.scrollToBottom();
|
|
159
|
+
changed();
|
|
160
|
+
};
|
|
161
|
+
const toggleFps = () => {
|
|
162
|
+
if (fpsTimer) clearInterval(fpsTimer);
|
|
163
|
+
fpsTimer = null;
|
|
164
|
+
if (fps.toggle(images.visible())) fpsTimer = setInterval(() => {
|
|
165
|
+
fps.sample();
|
|
166
|
+
if (active) { renderer.invalidateRows(0,1); requestFrame(); }
|
|
167
|
+
},1000);
|
|
168
|
+
renderer.invalidateRows(0,1); requestFrame();
|
|
169
|
+
};
|
|
170
|
+
const commands = createCommands({fontSize:()=>renderer.fontSize,setFontSize,copy,paste,
|
|
171
|
+
switchVT: number => vtMenu?.select(number)});
|
|
172
|
+
if (vtMenu) menu = createVtMenu({
|
|
173
|
+
...vtMenu, width: screen.width, height: screen.height, rowHeight: renderer.cellHeight,
|
|
174
|
+
changed,
|
|
175
|
+
controls: {fpsEnabled:()=>fps.enabled,toggleFps,fontSize:()=>renderer.fontSize,cellHeight:()=>renderer.cellHeight,execute:line=>commands.execute(line)},
|
|
176
|
+
});
|
|
117
177
|
const suspend = () => {
|
|
178
|
+
keyboardProtocol?.blur();
|
|
179
|
+
selection.stop();
|
|
118
180
|
active = false;
|
|
181
|
+
menu?.close();
|
|
119
182
|
if (frameTimer) clearTimeout(frameTimer);
|
|
120
183
|
frameTimer = null;
|
|
121
184
|
};
|
|
122
185
|
const resume = () => {
|
|
123
186
|
if (active) return;
|
|
124
187
|
active = true;
|
|
188
|
+
consoleKeyboard?.clearHeld();
|
|
125
189
|
renderer.invalidate();
|
|
126
190
|
paint();
|
|
127
191
|
};
|
|
@@ -138,17 +202,25 @@ export const createSession = ({
|
|
|
138
202
|
let queue = Promise.resolve();
|
|
139
203
|
const pump = (chunk) => {
|
|
140
204
|
queue = queue.then(async () => {
|
|
205
|
+
if (closed) return;
|
|
141
206
|
const { events } = kitty.consume(chunk);
|
|
142
207
|
for (const event of events) {
|
|
208
|
+
if (closed) return;
|
|
143
209
|
if (event.kind === "plain") {
|
|
210
|
+
// A selection is a snapshot of the displayed cells. Drop it before
|
|
211
|
+
// output scrolls/replaces those cells, rather than copying stale rows.
|
|
212
|
+
selection.clear();
|
|
144
213
|
await writeTerminal(term, event.data);
|
|
145
214
|
continue;
|
|
146
215
|
}
|
|
147
216
|
const buffer = term.buffer.active;
|
|
148
217
|
const graphic = kitty.parsePacket(event.packet, { row: buffer.cursorY + 1, col: buffer.cursorX + 1 });
|
|
149
218
|
if (!graphic) continue;
|
|
219
|
+
using frame = graphic[Symbol.dispose] ? graphic : null;
|
|
150
220
|
const motion = await images.handle(graphic);
|
|
221
|
+
if (closed) return;
|
|
151
222
|
if (motion) await writeTerminal(term, motion);
|
|
223
|
+
if (closed) return;
|
|
152
224
|
const ack = kitty.acknowledge(graphic);
|
|
153
225
|
if (ack) backend.write(Buffer.from(ack, "utf8"));
|
|
154
226
|
}
|
|
@@ -169,6 +241,8 @@ export const createSession = ({
|
|
|
169
241
|
|
|
170
242
|
// Keyboard bytes from the console, rewritten for the program's key mode.
|
|
171
243
|
const input = (bytes) => {
|
|
244
|
+
if (!active) return;
|
|
245
|
+
if (menu?.input(bytes)) return;
|
|
172
246
|
const applicationCursorKeys = Boolean(term._core?.coreService?.decPrivateModes?.applicationCursorKeys);
|
|
173
247
|
const text = translateKeys(bytes.toString("latin1"), { applicationCursorKeys });
|
|
174
248
|
backend.write(Buffer.from(text, "latin1"));
|
|
@@ -180,6 +254,31 @@ export const createSession = ({
|
|
|
180
254
|
resetBlink();
|
|
181
255
|
requestFrame();
|
|
182
256
|
};
|
|
257
|
+
const attachKeyboard = device => {
|
|
258
|
+
consoleKeyboard = device;
|
|
259
|
+
keyboardProtocol = createKeyboard({
|
|
260
|
+
term,
|
|
261
|
+
setEnhanced: value => device.setEnhanced(value),
|
|
262
|
+
write: text => {
|
|
263
|
+
backend.write(Buffer.from(text,"utf8"));
|
|
264
|
+
term.scrollToBottom(); resetBlink(); changed();
|
|
265
|
+
},
|
|
266
|
+
localInput: (event, text) => {
|
|
267
|
+
if (!menu?.visible) return false;
|
|
268
|
+
if (text) menu.input(Buffer.from(text,"utf8"));
|
|
269
|
+
return true;
|
|
270
|
+
},
|
|
271
|
+
switchVT: number => { Promise.resolve(vtMenu?.select(number)).catch(onError); },
|
|
272
|
+
switchRelative: delta => {
|
|
273
|
+
try {
|
|
274
|
+
const number = Math.max(1,Math.min(63,activeConsole({fd:device.fd})+delta));
|
|
275
|
+
Promise.resolve(vtMenu?.select(number)).catch(onError);
|
|
276
|
+
} catch (error) { onError(error); }
|
|
277
|
+
},
|
|
278
|
+
onError,
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
const keyEvent = event => { if (active) keyboardProtocol?.event(event); };
|
|
183
282
|
|
|
184
283
|
// Turns a pointer report from mouse.js into the mouse sequences the
|
|
185
284
|
// program asked for. xterm.js's CoreMouseService owns the protocol: it
|
|
@@ -221,11 +320,18 @@ export const createSession = ({
|
|
|
221
320
|
if (!active) return;
|
|
222
321
|
pointer = { x: report.x, y: report.y };
|
|
223
322
|
requestFrame();
|
|
323
|
+
if (menu?.pointer(report)) {
|
|
324
|
+
selection.stop();
|
|
325
|
+
keyboardProtocol?.blur();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
224
328
|
const service = term._core?.coreMouseService;
|
|
225
329
|
if (!service?.areMouseEventsActive) {
|
|
226
330
|
if (report.wheel) wheelScroll(report.wheel);
|
|
331
|
+
selection.pointer(report);
|
|
227
332
|
return;
|
|
228
333
|
}
|
|
334
|
+
selection.clear();
|
|
229
335
|
const send = (button, action) => {
|
|
230
336
|
try {
|
|
231
337
|
service.triggerMouseEvent(terminalMouseEvent(report, renderer, button, action));
|
|
@@ -278,12 +384,18 @@ export const createSession = ({
|
|
|
278
384
|
const close = () => {
|
|
279
385
|
if (closed) return;
|
|
280
386
|
closed = true;
|
|
387
|
+
active = false;
|
|
281
388
|
if (blinkTimer) clearInterval(blinkTimer);
|
|
389
|
+
if (fpsTimer) clearInterval(fpsTimer);
|
|
390
|
+
fpsTimer = null;
|
|
282
391
|
if (frameTimer) clearTimeout(frameTimer);
|
|
283
392
|
frameTimer = null;
|
|
284
393
|
backend.close();
|
|
394
|
+
try { keyboardProtocol?.dispose(); } catch (error) { onError(error); }
|
|
395
|
+
keyboardProtocol = null;
|
|
285
396
|
pointerDevice?.close();
|
|
286
397
|
pointerDevice = null;
|
|
398
|
+
kitty.dispose();
|
|
287
399
|
images.delete();
|
|
288
400
|
renderer.delete();
|
|
289
401
|
term.dispose();
|
|
@@ -293,6 +405,7 @@ export const createSession = ({
|
|
|
293
405
|
return {
|
|
294
406
|
term, backend, renderer, images, kitty,
|
|
295
407
|
paint, requestFrame, suspend, resume, settle, input, exited, close, attachPointer, reportMouse, wheelScroll,
|
|
408
|
+
attachKeyboard, keyEvent, selection, setFontSize, commands,
|
|
296
409
|
};
|
|
297
410
|
};
|
|
298
411
|
|
|
@@ -308,21 +421,52 @@ export const runTerminal = async ({ console: consoleDevice = null, device = "/de
|
|
|
308
421
|
|
|
309
422
|
return runGraphics(async (display, { onRestore, onRelease, onAcquire }) => {
|
|
310
423
|
const screen = openCanvas(CanvasKit, display);
|
|
311
|
-
|
|
424
|
+
let switching = false;
|
|
425
|
+
let keyboard = null;
|
|
426
|
+
const session = createSession({ CanvasKit, screen, fonts, ...options, vtMenu: {
|
|
427
|
+
list: () => {
|
|
428
|
+
const current = keyboard ? activeConsole(keyboard) : null;
|
|
429
|
+
return listVirtualTerminals().map(entry => ({...entry,current:entry.number===current}));
|
|
430
|
+
},
|
|
431
|
+
create: async fontSize => {
|
|
432
|
+
// Re-scan at activation, since another session may have opened while
|
|
433
|
+
// the menu was visible. The launcher detaches onto the new console;
|
|
434
|
+
// its normal VT ownership checks still apply.
|
|
435
|
+
const number = nextVirtualTerminal();
|
|
436
|
+
const child = Bun.spawn([process.execPath, `${import.meta.dir}/../../bin/bunterm`,
|
|
437
|
+
String(number), "-s", String(fontSize), "--fb", device],
|
|
438
|
+
{ stdin: "ignore", stdout: "ignore", stderr: "pipe" });
|
|
439
|
+
const [status, error] = await Promise.all([child.exited, new Response(child.stderr).text()]);
|
|
440
|
+
if (status !== 0) throw new Error(error.trim() || "無法啟動 / Failed to start bunterm");
|
|
441
|
+
},
|
|
442
|
+
select: async number => {
|
|
443
|
+
if (switching) return;
|
|
444
|
+
switching = true;
|
|
445
|
+
try {
|
|
446
|
+
// Waiting in this process would block its VT_PROCESS signal handlers.
|
|
447
|
+
const child = Bun.spawn([process.execPath, `${import.meta.dir}/../../bin/chvt`, String(number)],
|
|
448
|
+
{ stdin: "ignore", stdout: "ignore", stderr: "pipe" });
|
|
449
|
+
const [status, error] = await Promise.all([child.exited, new Response(child.stderr).text()]);
|
|
450
|
+
if (status !== 0) console.error(`bunterm: ${error.trim() || "VT switch failed"}`);
|
|
451
|
+
} catch (error) { console.error(`bunterm: VT switch: ${error.message}`); }
|
|
452
|
+
finally { switching = false; }
|
|
453
|
+
},
|
|
454
|
+
} });
|
|
312
455
|
onRelease(session.suspend);
|
|
313
456
|
// Coming back from another console (Ctrl-Alt-Fn): fbcon or another
|
|
314
457
|
// bunterm has drawn over the framebuffer, so repaint everything.
|
|
315
458
|
onAcquire(session.resume);
|
|
316
|
-
let keyboard = null;
|
|
317
459
|
try {
|
|
318
460
|
keyboard = openKeyboard({
|
|
319
461
|
device: consoleDevice,
|
|
320
462
|
onData: session.input,
|
|
463
|
+
onKey: session.keyEvent,
|
|
321
464
|
onError: (error) => {
|
|
322
465
|
console.error(`bunterm: keyboard: ${error.message}`);
|
|
323
466
|
session.backend.close();
|
|
324
467
|
},
|
|
325
468
|
});
|
|
469
|
+
session.attachKeyboard(keyboard);
|
|
326
470
|
onRestore(() => keyboard.close());
|
|
327
471
|
if (options.mouse !== false) {
|
|
328
472
|
// A pointer that cannot be opened is reported and the session
|
|
@@ -338,9 +482,10 @@ export const runTerminal = async ({ console: consoleDevice = null, device = "/de
|
|
|
338
482
|
}
|
|
339
483
|
return await session.exited;
|
|
340
484
|
} finally {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
485
|
+
try { session.close(); }
|
|
486
|
+
finally {
|
|
487
|
+
try { keyboard?.close(); } finally { screen.close(); }
|
|
488
|
+
}
|
|
344
489
|
}
|
|
345
490
|
}, { device, console: consolePaths, prepareConsole: claimVirtualConsole });
|
|
346
491
|
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Local overlay; none of its input is sent to the PTY.
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
|
|
4
|
+
export function createVtMenu({ list, select, create = null, changed, width, height, rowHeight, controls = null }) {
|
|
5
|
+
let row = Math.max(24, Math.ceil(rowHeight + 8));
|
|
6
|
+
let capacity = Math.max(1, Math.floor(height / row) - 2);
|
|
7
|
+
const panelWidth = Math.min(width, 420);
|
|
8
|
+
let entries = null, selected = 0, offset = 0, swallow = false;
|
|
9
|
+
let command = [], caret = 0, editing = false, status = "", escape = "";
|
|
10
|
+
let decoder = new StringDecoder("utf8");
|
|
11
|
+
let creating = false;
|
|
12
|
+
const layout = () => {
|
|
13
|
+
row = Math.max(24, Math.ceil((controls?.cellHeight?.() ?? rowHeight) + 8));
|
|
14
|
+
capacity = Math.max(1, Math.floor(height / row) - 2);
|
|
15
|
+
if (selected >= offset + capacity) offset = selected - capacity + 1;
|
|
16
|
+
};
|
|
17
|
+
const close = () => { entries = null; editing = false; changed(); };
|
|
18
|
+
const move = delta => {
|
|
19
|
+
selected = Math.max(0, Math.min(entries.length - 1, selected + delta));
|
|
20
|
+
offset = Math.max(0, Math.min(offset, selected));
|
|
21
|
+
if (selected >= offset + capacity) offset = selected - capacity + 1;
|
|
22
|
+
changed();
|
|
23
|
+
};
|
|
24
|
+
const run = (line) => {
|
|
25
|
+
// Promise.resolve().then also catches synchronous callbacks.
|
|
26
|
+
Promise.resolve().then(() => controls.execute(line)).then(message => {
|
|
27
|
+
status = message || ""; changed();
|
|
28
|
+
}).catch(error => { status = error.message; changed(); });
|
|
29
|
+
};
|
|
30
|
+
const choose = (right = false) => {
|
|
31
|
+
const target = entries[selected];
|
|
32
|
+
if (target?.kind === "create") {
|
|
33
|
+
if (creating) return;
|
|
34
|
+
creating = true;
|
|
35
|
+
status = "啟動中 / Starting…"; changed();
|
|
36
|
+
Promise.resolve().then(() => create(controls.fontSize())).then(() => close())
|
|
37
|
+
.catch(error => { status = error.message; changed(); })
|
|
38
|
+
.finally(() => { creating = false; });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (target?.kind === "font") { run(`font ${right ? "+" : "-"}`); return; }
|
|
42
|
+
if (target?.kind === "clipboard") {
|
|
43
|
+
run(right ? "paste" : "copy");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (target?.kind === "command") { editing = true; changed(); return; }
|
|
47
|
+
close(); if (target) select(target.number);
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
get visible() { return entries !== null; },
|
|
51
|
+
close,
|
|
52
|
+
pointer(report) {
|
|
53
|
+
layout();
|
|
54
|
+
if (swallow && !entries) {
|
|
55
|
+
if (!Object.values(report.buttons).some(Boolean)) swallow = false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (!entries) {
|
|
59
|
+
if (report.x < 0 || report.y < 0 || report.x >= 2 || report.y >= 2 || !report.pressed.includes("left")) return false;
|
|
60
|
+
entries = [...(controls ? [{kind:"font"}, {kind:"clipboard"}, {kind:"command"}] : []), ...list(),
|
|
61
|
+
...(create && controls ? [{kind:"create",label:"+ 新增 bunterm / New bunterm"}] : [])];
|
|
62
|
+
selected = 0; offset = 0; swallow = true; status = ""; escape = "";
|
|
63
|
+
decoder = new StringDecoder("utf8"); changed(); return true;
|
|
64
|
+
}
|
|
65
|
+
swallow = Object.values(report.buttons).some(Boolean);
|
|
66
|
+
if (report.wheel) move(-Math.sign(report.wheel));
|
|
67
|
+
const index = Math.floor(report.y / row) - 1 + offset;
|
|
68
|
+
const inside = report.x >= 0 && report.x < panelWidth && report.y >= row && index >= offset && index < Math.min(entries.length, offset + capacity);
|
|
69
|
+
if (inside && report.moved && !editing) { selected = index; changed(); }
|
|
70
|
+
if (report.pressed.includes("left")) {
|
|
71
|
+
swallow = true;
|
|
72
|
+
if (controls?.toggleFps && report.y >= 0 && report.y < row &&
|
|
73
|
+
report.x >= Math.max(0,panelWidth-112) && report.x < panelWidth) {
|
|
74
|
+
controls.toggleFps(); changed(); return true;
|
|
75
|
+
}
|
|
76
|
+
if (inside) {
|
|
77
|
+
selected = index; editing = false;
|
|
78
|
+
if (entries[index].kind !== "font" || report.x < 64 || report.x >= panelWidth-64)
|
|
79
|
+
choose(report.x >= panelWidth/2);
|
|
80
|
+
else changed();
|
|
81
|
+
} else close();
|
|
82
|
+
} else if (report.pressed.includes("right")) { swallow = true; close(); }
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
input(bytes) {
|
|
86
|
+
if (!entries) return false;
|
|
87
|
+
layout();
|
|
88
|
+
const text = decoder.write(bytes);
|
|
89
|
+
if (text === "\x1b" && !escape || text === "\x03") { close(); return true; }
|
|
90
|
+
// Parse multiple keys in one console read, retaining incomplete CSI/SS3.
|
|
91
|
+
const input = escape + text; escape = "";
|
|
92
|
+
for (let i = 0; i < input.length;) {
|
|
93
|
+
let key = String.fromCodePoint(input.codePointAt(i));
|
|
94
|
+
if (key === "\x1b") {
|
|
95
|
+
const rest = input.slice(i);
|
|
96
|
+
const match = /^\x1b(?:\[[0-9;]*[A-Za-z~]|O[A-Za-z])/.exec(rest);
|
|
97
|
+
if (!match) { if (/^\x1b(?:\[[0-9;]*|O)?$/.test(rest)) escape = rest; break; }
|
|
98
|
+
key = match[0];
|
|
99
|
+
}
|
|
100
|
+
i += key.length;
|
|
101
|
+
if (key === "\x03") { close(); break; }
|
|
102
|
+
if (key === "\t" && controls) {
|
|
103
|
+
editing = !editing;
|
|
104
|
+
if (editing && controls) { selected = 2; move(0); }
|
|
105
|
+
} else if (editing) {
|
|
106
|
+
if (key === "\r" || key === "\n") { run(command.join("")); command = []; caret = 0; }
|
|
107
|
+
else if (key === "\x7f" || key === "\b") { if (caret) command.splice(--caret,1); }
|
|
108
|
+
else if (key === "\x1b[3~") command.splice(caret,1);
|
|
109
|
+
else if (["\x1b[D","\x1bOD"].includes(key)) caret = Math.max(0,caret-1);
|
|
110
|
+
else if (["\x1b[C","\x1bOC"].includes(key)) caret = Math.min(command.length,caret+1);
|
|
111
|
+
else if (["\x01","\x1b[H","\x1bOH"].includes(key)) caret = 0;
|
|
112
|
+
else if (["\x05","\x1b[F","\x1bOF"].includes(key)) caret = command.length;
|
|
113
|
+
else if (key === "\x15") { command = []; caret = 0; }
|
|
114
|
+
else if (key.codePointAt(0) >= 32 && !key.startsWith("\x1b") && command.length < 1024) command.splice(caret++,0,key);
|
|
115
|
+
} else if (["\x1b[A", "\x1bOA", "k"].includes(key)) move(-1);
|
|
116
|
+
else if (["\x1b[B", "\x1bOB", "j"].includes(key)) move(1);
|
|
117
|
+
else if (key === "\r" || key === "\n") { choose(); if (!entries) break; }
|
|
118
|
+
else if (controls && ["+","-","\x1b[C","\x1b[D"].includes(key) && entries[selected]?.kind === "font")
|
|
119
|
+
choose(key === "+" || key === "\x1b[C");
|
|
120
|
+
else if (controls && entries[selected]?.kind === "clipboard" && ["c","p","\x1b[C","\x1b[D"].includes(key))
|
|
121
|
+
choose(key === "p" || key === "\x1b[C");
|
|
122
|
+
}
|
|
123
|
+
changed();
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
draw(renderer) {
|
|
127
|
+
if (!entries) return;
|
|
128
|
+
layout();
|
|
129
|
+
const { canvas, CanvasKit } = renderer;
|
|
130
|
+
const visible = entries.slice(offset, offset + capacity);
|
|
131
|
+
const rect = (y, color) => canvas.drawRect(CanvasKit.XYWHRect(0, y, panelWidth, row), renderer.paintFor(color));
|
|
132
|
+
const text = (value, y, x = 8, span = panelWidth - 16, align = "left") => renderer.drawText(value, x, y + (row - renderer.cellHeight) / 2,
|
|
133
|
+
Math.max(1, Math.floor(span / renderer.cellWidth)), [240, 240, 240], {align});
|
|
134
|
+
canvas.save();
|
|
135
|
+
canvas.clipRect(CanvasKit.XYWHRect(0, 0, panelWidth, Math.min(height, (visible.length + 2) * row)), CanvasKit.ClipOp.Intersect, false);
|
|
136
|
+
rect(0, [35, 40, 48]);
|
|
137
|
+
const fpsWidth = controls?.toggleFps ? Math.min(panelWidth,112) : 0;
|
|
138
|
+
text(controls ? "bunterm" : "Switch terminal",0,8,Math.max(1,panelWidth-fpsWidth-16));
|
|
139
|
+
if (fpsWidth) {
|
|
140
|
+
canvas.drawRect(CanvasKit.XYWHRect(panelWidth-fpsWidth,2,fpsWidth-2,row-4),
|
|
141
|
+
renderer.paintFor(controls.fpsEnabled()?[35,100,110]:[55,65,75]));
|
|
142
|
+
text(controls.fpsEnabled() ? "[FPS ✓]" : "[FPS]",0,panelWidth-fpsWidth+4,fpsWidth-10,"center");
|
|
143
|
+
}
|
|
144
|
+
visible.forEach((entry, i) => {
|
|
145
|
+
const y = (i + 1) * row;
|
|
146
|
+
rect(y, i + offset === selected ? [45, 80, 125] : [25, 29, 35]);
|
|
147
|
+
if (entry.kind === "font") {
|
|
148
|
+
// Use equal, cell-aligned button widths. Centered text scales to
|
|
149
|
+
// fit at large font sizes instead of clipping the closing bracket.
|
|
150
|
+
const buttonWidth = Math.max(1,Math.floor(48/renderer.cellWidth))*renderer.cellWidth;
|
|
151
|
+
const labelStart = buttonWidth+16;
|
|
152
|
+
text("[-]",y,8,buttonWidth,"center");
|
|
153
|
+
text(`🔤 字體 Font ${controls.fontSize()}`,y,labelStart,Math.max(1,panelWidth-labelStart*2),"center");
|
|
154
|
+
text("[+]",y,Math.max(8,panelWidth-8-buttonWidth),buttonWidth,"center");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (entry.kind === "clipboard") {
|
|
158
|
+
text("📋 複製 Copy",y,8,panelWidth/2-16);
|
|
159
|
+
text("📥 貼上 Paste",y,panelWidth/2+8,panelWidth/2-16);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (entry.kind === "command") {
|
|
163
|
+
canvas.drawRect(CanvasKit.XYWHRect(4,y+2,panelWidth-8,row-4),renderer.paintFor(editing?[80,170,220]:[85,95,105]));
|
|
164
|
+
canvas.drawRect(CanvasKit.XYWHRect(5,y+3,panelWidth-10,row-6),renderer.paintFor([225,230,235]));
|
|
165
|
+
const placeholder = !command.length && !editing;
|
|
166
|
+
const label = placeholder ? "Click to type a command…"
|
|
167
|
+
: `${command.slice(Math.max(0,caret-24),caret).join("")}${editing?"│":""}${command.slice(caret).join("")}`;
|
|
168
|
+
renderer.drawText(label,10,y+(row-renderer.cellHeight)/2,
|
|
169
|
+
Math.max(1,Math.floor((panelWidth-20)/renderer.cellWidth)),
|
|
170
|
+
placeholder?[100,110,120]:[20,25,30],{align:"left"});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const label = `${entry.label}${entry.current ? " (current)" : ""}`;
|
|
174
|
+
text(label, y);
|
|
175
|
+
});
|
|
176
|
+
const y = (visible.length + 1) * row;
|
|
177
|
+
rect(y, [35, 40, 48]); text(status || (controls ? "Tab: command · Enter · Esc" : "↑↓ / wheel · Enter · Esc"), y);
|
|
178
|
+
canvas.restore();
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// hold the console device open. Both facts live under /proc/<pid> and disappear
|
|
7
7
|
// with the process; there is no persistent pid or lock file to become stale.
|
|
8
8
|
|
|
9
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
9
|
+
import { readdirSync, readFileSync, readlinkSync, statSync } from "node:fs";
|
|
10
10
|
import { libc, cString, check } from "../dlopen.js";
|
|
11
11
|
|
|
12
12
|
const PR_SET_NAME = 15;
|
|
@@ -89,3 +89,29 @@ Switch between TTYs by Ctrl+Alt+F1-12
|
|
|
89
89
|
try { setTaskName(previousName); } catch {}
|
|
90
90
|
};
|
|
91
91
|
};
|
|
92
|
+
|
|
93
|
+
// Discover live owners using the same task-name + device-fd contract as claim.
|
|
94
|
+
export const listVirtualTerminals = () => {
|
|
95
|
+
let initial = 1;
|
|
96
|
+
try {
|
|
97
|
+
initial = virtualConsoleNumber(readlinkSync("/proc/1/fd/0")) ?? 1;
|
|
98
|
+
} catch {}
|
|
99
|
+
const entries = new Map([[initial, { number: initial, label: `tty${initial} — initial console` }]]);
|
|
100
|
+
for (const pid of readdirSync("/proc").filter(name => /^\d+$/.test(name))) {
|
|
101
|
+
try {
|
|
102
|
+
const match = /^bunterm:tty([1-9][0-9]*)$/.exec(readFileSync(`/proc/${pid}/comm`, "utf8").trim());
|
|
103
|
+
if (!match) continue;
|
|
104
|
+
const number = Number(match[1]);
|
|
105
|
+
if (number > 63 || !processHoldsDevice(pid, statSync(`/dev/tty${number}`).rdev)) continue;
|
|
106
|
+
entries.set(number, { number, label: `tty${number} — bunterm${number === initial ? " / initial console" : ""}` });
|
|
107
|
+
} catch {} // processes can exit during the scan
|
|
108
|
+
}
|
|
109
|
+
return [...entries.values()].sort((a, b) => a.number - b.number);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Match the menu: append after the last known session, reserving tty1.
|
|
113
|
+
export const nextVirtualTerminal = (entries = listVirtualTerminals()) => {
|
|
114
|
+
const number = Math.max(1, ...entries.map(entry => entry.number)) + 1;
|
|
115
|
+
if (number > 63) throw new Error("沒有可用 tty / No next tty available");
|
|
116
|
+
return number;
|
|
117
|
+
};
|
package/initramfs/lib/canvas.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {rawImageData, rawImageOpaque, skiaImage} from './raw-image.js';
|
|
1
2
|
// /lib/canvas.js — CanvasKit (Skia in WebAssembly) on the Linux framebuffer.
|
|
2
3
|
//
|
|
3
4
|
// Skia draws into a buffer inside the wasm heap that already has the
|
|
@@ -80,6 +81,17 @@ export const openCanvas = (CanvasKit, display) => {
|
|
|
80
81
|
memory.toTypedArray().set(previousPixels.subarray(0, length));
|
|
81
82
|
display.pixels = memory.toTypedArray();
|
|
82
83
|
let open = true;
|
|
84
|
+
let cachedImage = null, cachedOpaque = false;
|
|
85
|
+
let imageMemory = null, imageCapacity = 0;
|
|
86
|
+
const directRaw = (image, rect, source) => {
|
|
87
|
+
const raw = rawImageData(image);
|
|
88
|
+
if (!raw || display.bitsPerPixel !== 32 || raw.colorType !== colorType) return null;
|
|
89
|
+
const [left, top, right, bottom] = rect, [sx, sy, ex, ey] = source;
|
|
90
|
+
if (![...rect, ...source].every(Number.isInteger)
|
|
91
|
+
|| right - left !== ex - sx || bottom - top !== ey - sy
|
|
92
|
+
|| sx < 0 || sy < 0 || ex > raw.width || ey > raw.height || ex <= sx || ey <= sy) return null;
|
|
93
|
+
return rawImageOpaque(raw) ? raw : null;
|
|
94
|
+
};
|
|
83
95
|
return {
|
|
84
96
|
CanvasKit,
|
|
85
97
|
display,
|
|
@@ -88,6 +100,90 @@ export const openCanvas = (CanvasKit, display) => {
|
|
|
88
100
|
colorType,
|
|
89
101
|
width: display.width,
|
|
90
102
|
height: display.height,
|
|
103
|
+
// Only exact, opaque 32-bit copies bypass Skia. Keep at most one decoded
|
|
104
|
+
// image (32 MiB); scaled, translucent and 16-bit images use normal drawing.
|
|
105
|
+
canDrawRawImage(image, rect, source) { return Boolean(directRaw(image, rect, source)); },
|
|
106
|
+
drawOpaqueImage(image, rect, source, bands) {
|
|
107
|
+
if (!open) throw new Error("canvas is closed");
|
|
108
|
+
const [left, top, right, bottom] = rect;
|
|
109
|
+
const [sx, sy, ex, ey] = source;
|
|
110
|
+
const width = image.width(), height = image.height();
|
|
111
|
+
if (display.bitsPerPixel !== 32 || ![...rect, ...source].every(Number.isInteger)
|
|
112
|
+
|| right - left !== ex - sx || bottom - top !== ey - sy
|
|
113
|
+
|| sx < 0 || sy < 0 || ex > width || ey > height
|
|
114
|
+
|| ex <= sx || ey <= sy || width * height * 4 > 32 * 1024 * 1024) return false;
|
|
115
|
+
const raw = directRaw(image, rect, source);
|
|
116
|
+
if (!raw) image = skiaImage(image);
|
|
117
|
+
if (!raw && cachedImage !== image) {
|
|
118
|
+
cachedImage = image;
|
|
119
|
+
cachedOpaque = false;
|
|
120
|
+
const size = width * height * 4;
|
|
121
|
+
if (size > imageCapacity) {
|
|
122
|
+
if (imageMemory) CanvasKit.Free(imageMemory);
|
|
123
|
+
imageMemory = CanvasKit.Malloc(Uint8Array, size);
|
|
124
|
+
imageCapacity = size;
|
|
125
|
+
}
|
|
126
|
+
// Supplying a destination bypasses CanvasKit's JS return-type switch,
|
|
127
|
+
// which otherwise rejects BGRA and copies supported formats out of WASM.
|
|
128
|
+
const info = {width, height, colorType,
|
|
129
|
+
alphaType: CanvasKit.AlphaType.Unpremul, colorSpace: CanvasKit.ColorSpace.SRGB};
|
|
130
|
+
let pixels = image.readPixels(0, 0, info, imageMemory, width * 4);
|
|
131
|
+
let swap = false;
|
|
132
|
+
if (!pixels && colorType === CanvasKit.ColorType.BGRA_8888) {
|
|
133
|
+
pixels = image.readPixels(0, 0, {...info, colorType: CanvasKit.ColorType.RGBA_8888}, imageMemory, width * 4);
|
|
134
|
+
swap = true;
|
|
135
|
+
}
|
|
136
|
+
if (pixels) {
|
|
137
|
+
cachedOpaque = true;
|
|
138
|
+
for (let i = 0; i < size; i += 4) {
|
|
139
|
+
if (pixels[i + 3] !== 255) { cachedOpaque = false; break; }
|
|
140
|
+
if (swap) {
|
|
141
|
+
const red = pixels[i]; pixels[i] = pixels[i + 2]; pixels[i + 2] = red;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!raw && !cachedOpaque) return false;
|
|
147
|
+
surface.flush();
|
|
148
|
+
// Drawing elsewhere can grow the WASM heap between cache hits.
|
|
149
|
+
const sourcePixels = raw ? raw.pixels : imageMemory.toTypedArray();
|
|
150
|
+
const target = memory.toTypedArray();
|
|
151
|
+
const x0 = Math.max(0, left), x1 = Math.min(display.width, right);
|
|
152
|
+
if (x1 <= x0) return true;
|
|
153
|
+
for (const [bandTop, bandBottom] of bands) {
|
|
154
|
+
const y0 = Math.max(0, top, bandTop), y1 = Math.min(display.height, bottom, bandBottom);
|
|
155
|
+
for (let y = y0; y < y1; y++) {
|
|
156
|
+
const offset = ((sy + y - top) * width + sx + x0 - left) * 4;
|
|
157
|
+
target.set(sourcePixels.subarray(offset, offset + (x1 - x0) * 4), y * display.stride + x0 * 4);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
},
|
|
162
|
+
// Save/restore small overlays in native pixel format, without decoding or
|
|
163
|
+
// channel conversion. Refresh the WASM view after every possible allocation.
|
|
164
|
+
saveRegion(left, top, right, bottom) {
|
|
165
|
+
if (!open) throw new Error("canvas is closed");
|
|
166
|
+
surface.flush();
|
|
167
|
+
const x = Math.max(0, Math.min(display.width, Math.floor(left))), y = Math.max(0, Math.min(display.height, Math.floor(top)));
|
|
168
|
+
const width = Math.max(0, Math.min(display.width, Math.ceil(right)) - x);
|
|
169
|
+
const height = Math.max(0, Math.min(display.height, Math.ceil(bottom)) - y);
|
|
170
|
+
const rowBytes = width * (display.bitsPerPixel / 8);
|
|
171
|
+
const pixels = new Uint8Array(rowBytes * height), source = memory.toTypedArray();
|
|
172
|
+
for (let row = 0; row < height; row++) {
|
|
173
|
+
const offset = (y + row) * display.stride + x * (display.bitsPerPixel / 8);
|
|
174
|
+
pixels.set(source.subarray(offset, offset + rowBytes), row * rowBytes);
|
|
175
|
+
}
|
|
176
|
+
return {x, y, height, rowBytes, pixels};
|
|
177
|
+
},
|
|
178
|
+
restoreRegion(region) {
|
|
179
|
+
if (!open) throw new Error("canvas is closed");
|
|
180
|
+
surface.flush();
|
|
181
|
+
const target = memory.toTypedArray();
|
|
182
|
+
for (let row = 0; row < region.height; row++) {
|
|
183
|
+
target.set(region.pixels.subarray(row * region.rowBytes, (row + 1) * region.rowBytes),
|
|
184
|
+
(region.y + row) * display.stride + region.x * (display.bitsPerPixel / 8));
|
|
185
|
+
}
|
|
186
|
+
},
|
|
91
187
|
// Finishes pending Skia work and copies the frame to the framebuffer.
|
|
92
188
|
// toTypedArray() is re-read because wasm heap growth detaches old views.
|
|
93
189
|
flush() {
|
|
@@ -101,6 +197,9 @@ export const openCanvas = (CanvasKit, display) => {
|
|
|
101
197
|
close() {
|
|
102
198
|
if (!open) return;
|
|
103
199
|
open = false;
|
|
200
|
+
cachedImage = null; cachedOpaque = false;
|
|
201
|
+
if (imageMemory) CanvasKit.Free(imageMemory);
|
|
202
|
+
imageMemory = null; imageCapacity = 0;
|
|
104
203
|
previousPixels.set(memory.toTypedArray().subarray(0, previousPixels.length));
|
|
105
204
|
display.pixels = previousPixels;
|
|
106
205
|
surface.delete();
|