buninu-linux 0.3.1 → 0.5.1

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 (53) hide show
  1. package/LICENSES/Apache-2.0.txt +202 -0
  2. package/LICENSES/BSD-3-Clause-Skia.txt +29 -0
  3. package/LICENSES/Bitstream-Vera.txt +44 -0
  4. package/LICENSES/MIT-xterm.js.txt +21 -0
  5. package/LICENSES/OFL-1.1.txt +91 -0
  6. package/NOTICE.md +18 -1
  7. package/README.md +101 -9
  8. package/fetch-alpine.sh +6 -0
  9. package/graphics.md +218 -0
  10. package/initramfs/bin/bunterm +70 -0
  11. package/initramfs/bin/chroot +213 -0
  12. package/initramfs/bin/stripansi +27 -0
  13. package/initramfs/bin/tar +189 -14
  14. package/initramfs/buninu/CHANGELOG.md +40 -0
  15. package/initramfs/buninu/apps/jsgotty/gotty.js +4 -3
  16. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  17. package/initramfs/buninu/package.json +1 -1
  18. package/initramfs/init.js +80 -1
  19. package/initramfs/lib/bunterm/glyphs.js +446 -0
  20. package/initramfs/lib/bunterm/images.js +225 -0
  21. package/initramfs/lib/bunterm/input.js +140 -0
  22. package/initramfs/lib/bunterm/mouse.js +222 -0
  23. package/initramfs/lib/bunterm/renderer.js +469 -0
  24. package/initramfs/lib/bunterm/terminal.js +324 -0
  25. package/initramfs/lib/canvas.js +181 -0
  26. package/initramfs/lib/canvaskit/LICENSE +29 -0
  27. package/initramfs/lib/canvaskit/canvaskit.js +264 -0
  28. package/initramfs/lib/canvaskit/canvaskit.wasm +0 -0
  29. package/initramfs/lib/dlopen.js +18 -2
  30. package/initramfs/lib/document.js +5 -1
  31. package/initramfs/lib/fbdev.js +581 -0
  32. package/initramfs/lib/xterm/LICENSE +21 -0
  33. package/initramfs/lib/xterm/addon-unicode-graphemes.mjs +44 -0
  34. package/initramfs/lib/xterm/xterm-headless.mjs +46 -0
  35. package/initramfs/usr/share/doc/buninu-linux/bunterm.md +112 -0
  36. package/initramfs/usr/share/doc/buninu-linux/canvas.md +91 -0
  37. package/initramfs/usr/share/doc/buninu-linux/chroot.md +105 -0
  38. package/initramfs/usr/share/doc/buninu-linux/fbdev.md +113 -0
  39. package/initramfs/usr/share/doc/buninu-linux/tar.md +30 -6
  40. package/initramfs/usr/share/fonts/DejaVuSansMono-Bold.ttf +0 -0
  41. package/initramfs/usr/share/fonts/DejaVuSansMono.ttf +0 -0
  42. package/initramfs/usr/share/fonts/LICENSE-Apache-2.0.txt +202 -0
  43. package/initramfs/usr/share/fonts/LICENSE-DejaVu.txt +44 -0
  44. package/initramfs/usr/share/fonts/LICENSE-OFL-1.1.txt +91 -0
  45. package/initramfs/usr/share/fonts/NotoColorEmoji.ttf +0 -0
  46. package/initramfs/usr/share/fonts/NotoColorEmojiFlags.ttf +0 -0
  47. package/initramfs/usr/share/fonts/NotoSansCJK-Regular.ttc +0 -0
  48. package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted.ttf +0 -0
  49. package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted2.ttf +0 -0
  50. package/initramfs/usr/share/fonts/Roboto-Regular.ttf +0 -0
  51. package/initramfs/usr/share/fonts/fonts.json +28 -0
  52. package/package.json +2 -2
  53. package/run-qemu.sh +1 -1
@@ -0,0 +1,112 @@
1
+ # bunterm — a graphical terminal on the Linux framebuffer
2
+
3
+ `bunterm` runs a program on a pseudo-terminal and draws its screen on
4
+ `/dev/fb0` with Skia (CanvasKit), so a real Linux console gets an
5
+ xterm-compatible terminal with anti-aliased text, CJK, colour emoji, seamless
6
+ box drawing and kitty graphics protocol images — no X11, Wayland or GPU.
7
+
8
+ ```sh
9
+ bunterm # run /bin/sh on the console you are on
10
+ bunterm /dev/tty1 # use virtual console 1 for graphics and keys
11
+ bunterm --font-size 20
12
+ bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
13
+ bunterm --no-mouse # keyboard only
14
+ bunterm -h
15
+ ```
16
+
17
+ The program's `TERM` is `xterm-256color`, `COLORTERM=truecolor`,
18
+ `TERM_PROGRAM=bunterm` and `JSMDCUI_KITTY_MODE=extended` (so `jsmdcui` shows
19
+ Markdown images without `--kitty`). When it exits, the console returns to
20
+ text mode and `bunterm` exits with its status.
21
+
22
+ ## Options
23
+
24
+ | Option | Meaning |
25
+ | --- | --- |
26
+ | `/dev/ttyN` | The virtual console to use for graphics and the keyboard. If another console is on screen, the display switches to this one first and back when `bunterm` exits. Without it the process's own console and stdin are used, so run it from a VT (not a serial line or a PTY). |
27
+ | `-e command [argument...]` | The program to run; everything after `-e` belongs to it. Default `/bin/sh`. |
28
+ | `--font-size N` | Font size in pixels (default 16). The cell is 0.6 × N wide and 1.16 × N tall with DejaVu Sans Mono, so 1024×768 at 16 px gives 106 × 41 cells. |
29
+ | `--line-height F` | Multiplies the cell height (default 1). |
30
+ | `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
31
+ | `--no-blink` | A steady cursor. |
32
+ | `--no-mouse` | Do not attach a pointing device; nothing then reads `/dev/input`. |
33
+
34
+ ## What is drawn
35
+
36
+ - **Text** comes from `/usr/share/fonts`: DejaVu Sans Mono (regular and
37
+ bold) sets the grid; Noto Sans CJK TC, Noto Color Emoji (+ Flags) and Noto
38
+ Sans Symbols fill in what it lacks. A glyph is centred in the one or two
39
+ cells the terminal assigned it; wide characters take two.
40
+ - **Box drawing, block elements, shades and Powerline symbols** (U+2500–259F,
41
+ U+E0A0–E0BF, U+1FB70–1FB97) are drawn as vectors, ported from xterm.js, so
42
+ they join without seams at any size.
43
+ - **Attributes**: 16 / 256 / 24-bit colours, bold (brighter for the eight base
44
+ colours), italic, dim, underline, strikethrough, inverse, invisible.
45
+ - **Grapheme clusters** such as 🇹🇼 and 👨‍👩‍👧 occupy one cell each (xterm.js's
46
+ unicode-graphemes addon) and are shaped by Skia's paragraph engine.
47
+ - **Images**: the kitty graphics protocol (`ESC _ G … ESC \`) with direct
48
+ transmission (`t=d`), PNG (`f=100`) or raw RGB/RGBA (`f=24`/`32`), zlib
49
+ (`o=z`), chunking (`m=`), cropping (`x,y,w,h`), sizing in cells (`c,r`),
50
+ cell offsets (`X,Y`), placements (`p=`), deletion (`a=d`) and queries
51
+ (`a=q`). An image is anchored to its line and scrolls with the text; on the
52
+ alternate screen it stays at its row and is removed when the program
53
+ leaves that screen. Any encoding Skia decodes (PNG, JPEG, WebP, GIF) is
54
+ accepted under `f=100`, as with jsgotty's `U=` extension.
55
+
56
+ ## Keyboard
57
+
58
+ The console stays in the kernel's translated keyboard mode, so
59
+ `Ctrl-Alt-F2` and friends still switch consoles; coming back repaints the
60
+ screen. A few console-specific
61
+ sequences are rewritten to xterm's (`F1`–`F5`, `Home`, `End`), and arrow keys
62
+ follow the program's application cursor mode.
63
+
64
+ ## Mouse
65
+
66
+ Every pointing device under `/dev/input/event*` is opened and an arrow is
67
+ drawn on the screen. Clicks, drags and wheel notches reach the program as
68
+ the xterm mouse sequences it asked for: nothing is sent unless the program
69
+ enabled reporting (`DECSET 1000`, `1002` or `1003`), and what is sent follows
70
+ the mode it chose, SGR (`1006`) included. `jsmdcui`, `micro`, `htop`, `vim`
71
+ and anything else that speaks the protocol work with no configuration.
72
+
73
+ When the program is not reading the mouse, the wheel scrolls instead: back
74
+ through the scrollback on the normal screen, three lines per notch, and as
75
+ arrow keys on the alternate screen, which has no scrollback and is what a
76
+ pager expects. Typing returns to the live screen, and the text cursor is
77
+ hidden while it is scrolled out of view.
78
+
79
+ `--no-mouse` turns all of this off: the terminal then never looks at
80
+ `/dev/input` and never loads the mouse module.
81
+
82
+ `init.js` loads `evdev` and `psmouse` at boot, so a PS/2 mouse — which is
83
+ what QEMU's q35 machine and most PCs present — has a device node ready. A USB
84
+ mouse also needs the `usbhid` stack, which a `--real` image carries and
85
+ `cfg.all` loads. The pointer follows relative devices (a mouse) and absolute
86
+ ones (a tablet or touchscreen) alike; a device that cannot be opened is
87
+ reported and the session continues without it.
88
+
89
+ ## Limits
90
+
91
+ - Needs a framebuffer: `linux-lts` has one built in, while the default
92
+ `linux-virt` builds it as modules the image does not ship, so build with
93
+ `--linux-lts` or `--real` (efifb/simpledrm from UEFI).
94
+ - No selection, and no keyboard way to scroll back: the wheel is it. The
95
+ buffer keeps 1000 lines. A program that does not enable mouse reporting
96
+ sees no clicks — the arrow still moves, so the pointer is visibly alive.
97
+ `--no-mouse` removes the pointer entirely.
98
+ - A program that prints a terminal reply while the tty echoes (that is, one
99
+ not in raw mode) will see the reply echoed, as on any terminal.
100
+ - Kitty images are drawn above text; `z` ordering below text and animation
101
+ frames are not implemented, and file transmission (`t=f`/`t=t`) works only
102
+ for files under `/tmp`, `/dev/shm` or `$TMPDIR`.
103
+ - One framebuffer, one session. For panes and tabs run `jsmdcui` inside it.
104
+
105
+ ## See also
106
+
107
+ - `/lib/bunterm/terminal.js` — `createSession()` and `runTerminal()`, the
108
+ pieces `bunterm` is built from
109
+ - `/lib/canvas.js` — CanvasKit on the framebuffer (`canvas.md`)
110
+ - `/lib/fbdev.js` — the framebuffer itself (`fbdev.md`)
111
+ - `/buninu/apps/jsgotty/gotty.js` — the PTY backend and kitty packet parser
112
+ that are reused
@@ -0,0 +1,91 @@
1
+ # canvas — CanvasKit (Skia) on the Linux framebuffer
2
+
3
+ `/lib/canvas.js` runs Skia, compiled to WebAssembly as CanvasKit, on top of
4
+ `/lib/fbdev.js`. Skia rasterizes straight into a buffer that already has the
5
+ framebuffer's pixel layout, so a frame reaches `/dev/fb0` with one `memcpy`
6
+ and no per-pixel conversion in JavaScript.
7
+
8
+ ## Drawing with Skia
9
+
10
+ ```js
11
+ import { runGraphics } from "/lib/fbdev.js";
12
+ import { loadCanvasKit, openCanvas, loadFontSet } from "/lib/canvas.js";
13
+
14
+ const CanvasKit = await loadCanvasKit();
15
+ const fonts = loadFontSet(CanvasKit, "ui");
16
+
17
+ await runGraphics(async (display, { onRestore }) => {
18
+ const screen = openCanvas(CanvasKit, display);
19
+ const paint = new CanvasKit.Paint();
20
+ paint.setAntiAlias(true);
21
+ paint.setColor(CanvasKit.Color(64, 200, 255));
22
+ const font = new CanvasKit.Font(fonts.regular, 32);
23
+
24
+ screen.canvas.clear(CanvasKit.BLACK);
25
+ screen.canvas.drawCircle(display.width / 2, display.height / 2, 120, paint);
26
+ screen.canvas.drawText("Buninu 你好 😀", 40, 60, paint, font);
27
+ screen.flush();
28
+
29
+ await new Promise((resolve) => setTimeout(resolve, 3000));
30
+ screen.close();
31
+ });
32
+ ```
33
+
34
+ `screen.canvas` is a Skia `Canvas` with the full CanvasKit API: paths,
35
+ gradients, shaders, image filters, `drawImage`, `drawGlyphs`, `Paragraph`
36
+ layout with font fallback, PNG/JPEG decoding, and so on.
37
+
38
+ ## Exports
39
+
40
+ | Export | Purpose |
41
+ | --- | --- |
42
+ | `loadCanvasKit()` | Loads `/lib/canvaskit/canvaskit.wasm` once per process and resolves to the `CanvasKit` object. The wasm heap starts at 128 MiB. |
43
+ | `openCanvas(CanvasKit, display)` | Binds a raster surface to an open `Framebuffer`. Returns `{ canvas, surface, width, height, colorType, flush(), close() }`. While open, `display.pixels` is the surface's own memory, so `drawLine()`, `fillPolygon()` and `flush()` from `fbdev.js` keep working on the same frame. `close()` hands the last frame back to the JavaScript buffer. |
44
+ | `pickColorType(CanvasKit, display)` | The Skia `ColorType` matching the framebuffer: `BGRA_8888` for the usual 32-bit XRGB (efifb, simpledrm), `RGBA_8888`, `RGB_565`; `null` for anything else, which `openCanvas` rejects. |
45
+ | `loadFontSet(CanvasKit, name)` | A font set from `/usr/share/fonts/fonts.json` (`"terminal"` or `"ui"`) as `{ regular, bold, fallback, typefaces, fontMgr, families, delete() }`. `fontMgr` and `families` are what `ParagraphStyle` wants; a collection such as Noto Sans CJK registers every face and yields its preferred one (TC). |
46
+ | `fontCatalogue()`, `fontPath(name)`, `loadFontData(name)` | The parsed `fonts.json`, a font's path, and its bytes (cached). |
47
+
48
+ ## Formats
49
+
50
+ The framebuffer reports its channels as bit offsets. Skia can rasterize
51
+ directly into three of the layouts Linux uses:
52
+
53
+ | Framebuffer | Skia |
54
+ | --- | --- |
55
+ | 32 bpp, red@16 green@8 blue@0 (XRGB8888) | `BGRA_8888` |
56
+ | 32 bpp, red@0 green@8 blue@16 (XBGR8888) | `RGBA_8888` |
57
+ | 16 bpp, 5-6-5 | `RGB_565` |
58
+
59
+ For a 24 bpp or paletted framebuffer, draw with the HTML canvas emulation
60
+ instead and copy through `blitImageData()`:
61
+
62
+ ```js
63
+ const canvas = CanvasKit.MakeCanvas(display.width, display.height);
64
+ const ctx = canvas.getContext("2d");
65
+ ctx.fillStyle = "#40c8ff";
66
+ ctx.fillRect(0, 0, 200, 100);
67
+ blitImageData(display, ctx.getImageData(0, 0, display.width, display.height));
68
+ flush(display);
69
+ ```
70
+
71
+ That path converts every pixel in JavaScript and is fine for static screens.
72
+
73
+ ## Fonts
74
+
75
+ `/usr/share/fonts/fonts.json` lists the bundled files and two sets:
76
+
77
+ | Set | Regular | Fallback order |
78
+ | --- | --- | --- |
79
+ | `terminal` | DejaVu Sans Mono (+ Bold) | Noto Color Emoji, Noto Color Emoji Flags, Noto Sans CJK, Noto Sans Symbols, Noto Sans Symbols2 |
80
+ | `ui` | Roboto | Noto Sans CJK, Noto Color Emoji, Noto Color Emoji Flags, Noto Sans Symbols, Noto Sans Symbols2 |
81
+
82
+ Files load on first use and are copied into the wasm heap; the CJK
83
+ collection is 32 MiB, so a program that never draws CJK text should not
84
+ load a set containing it. Each file's license sits beside it.
85
+
86
+ ## See also
87
+
88
+ - `/lib/canvas.js`, `/lib/canvaskit/` (CanvasKit 0.41.1, BSD-3-Clause)
89
+ - `/lib/fbdev.js` — `fbdev.md`
90
+ - `/lib/bunterm/` — the terminal built on this (`bunterm.md`)
91
+ - CanvasKit API: https://skia.org/docs/user/modules/canvaskit/
@@ -0,0 +1,105 @@
1
+ # chroot — run a command inside another root directory
2
+
3
+ `/bin/chroot` is a Bun script that calls `chroot(2)` and `mount(2)` through
4
+ `/lib/dlopen.js`.
5
+
6
+ Unlike the traditional `chroot`, it prepares the new root first, so a
7
+ freshly unpacked root filesystem is usable straight away, and it undoes
8
+ every mount it made when the command exits.
9
+
10
+ ## Synopsis
11
+
12
+ ```sh
13
+ chroot NEWROOT [COMMAND [ARGUMENT...]]
14
+ chroot [-n] [-k] [-v] NEWROOT [COMMAND [ARGUMENT...]]
15
+ chroot -h | --help
16
+ ```
17
+
18
+ With no COMMAND it runs `$SHELL` if that exists inside NEWROOT, otherwise
19
+ `/bin/sh`, interactively when the terminal is one.
20
+
21
+ ## What it mounts
22
+
23
+ | Inside NEWROOT | What | Why |
24
+ |---|---|---|
25
+ | `/proc` | a fresh `proc` | path resolution, `ps`, anything reading `/proc/self` |
26
+ | `/sys` | a fresh `sysfs` | device and network information |
27
+ | `/dev` | recursive bind of the host's `/dev`, made a slave mount | every device node, including `/dev/ptmx`, without a second devtmpfs |
28
+ | `/dev/pts` | `devpts`, only if the bind brought none | pseudo-terminals for shells and editors |
29
+ | `/run` | `tmpfs` | runtime state |
30
+ | `/tmp` | `tmpfs` | scratch space that does not touch the root filesystem |
31
+ | `/etc/resolv.conf` | bind of the host's file | name resolution, without editing anything inside |
32
+
33
+ A mount point that already exists as a mount is left alone. A directory that
34
+ is missing is created; if the root filesystem is read-only and that fails,
35
+ that one mount is skipped and the rest still happen — use `-v` to see which.
36
+
37
+ Making `/dev` a slave mount means mounts appearing inside the root do not
38
+ propagate back to the host, and unmounting it afterwards cannot take the
39
+ host's `/dev` with it.
40
+
41
+ ## Options
42
+
43
+ | Option | Meaning |
44
+ |---|---|
45
+ | `-n`, `--no-mount` | Do not mount anything: the behaviour of the traditional `chroot`. Note that Bun itself cannot start without `/proc` and `/dev/urandom`, so `-n` suits statically linked programs. |
46
+ | `-k`, `--keep-mounts` | Leave the mounts in place after the command exits, to enter the same root again cheaply. Clean up later with `umount -R`. |
47
+ | `-v`, `--verbose` | Report every mount and unmount. |
48
+ | `--` | End of options; everything after it is NEWROOT and COMMAND. |
49
+
50
+ ## Examples
51
+
52
+ ```sh
53
+ # Mount a disk and enter the system on it
54
+ mount /dev/vda2 /mnt --mkdir
55
+ chroot /mnt
56
+
57
+ # Run one command and leave
58
+ chroot /mnt /bin/bun --version
59
+
60
+ # Watch what is set up and taken down
61
+ chroot -v /mnt /bin/sh -c 'ls /proc | head -3'
62
+
63
+ # Traditional behaviour: nothing is mounted for you
64
+ chroot -n /mnt /bin/sh
65
+
66
+ # Keep the mounts for repeated visits
67
+ chroot -k /mnt /bin/sh -c 'true'
68
+ chroot -k /mnt
69
+ umount -R /mnt
70
+ ```
71
+
72
+ ## Cleanup
73
+
74
+ When the command exits, every mount this invocation made is unmounted,
75
+ deepest first, including anything that appeared under the recursive `/dev`
76
+ bind. A mount that is still busy — a process left running inside, a shell
77
+ with its working directory there — is detached instead (`MNT_DETACH`), so
78
+ the kernel releases it once its last user goes away. The same cleanup runs
79
+ if `chroot(2)` itself fails or the command cannot be started, and on
80
+ `SIGTERM` or `SIGHUP`.
81
+
82
+ `Ctrl-C` goes to the command, not to `chroot`, so the mounts are still
83
+ removed after an interrupted program.
84
+
85
+ ## Limits
86
+
87
+ - Needs to be superuser: `chroot(2)` requires `CAP_SYS_CHROOT`, and the
88
+ mounts require `CAP_SYS_ADMIN`. Buninu normally runs as root.
89
+ - The new root must contain the program you ask for and whatever it links
90
+ against; `chroot /mnt /bin/sh` fails with "not found" if `/mnt/bin/sh`
91
+ is not there.
92
+ - Only the filesystem view changes. Processes, network, users and the
93
+ hostname are shared with the host; this is not a container.
94
+ - `-k` leaves mounts behind on purpose. `umount -R NEWROOT` removes them.
95
+
96
+ ## Exit status
97
+
98
+ The command's own status, or `125` if `chroot` itself failed, `126` if the
99
+ command was found but could not be run, and `127` if it was not found.
100
+
101
+ ## See also
102
+
103
+ - Source: `/bin/chroot`
104
+ - `mount --help`, `umount --help`
105
+ - Shared native binding: `/lib/dlopen.js`
@@ -0,0 +1,113 @@
1
+ # fbdev — draw directly on the Linux framebuffer
2
+
3
+ `/lib/fbdev.js` is both an importable drawing module and a small executable
4
+ demo. It uses `/dev/fb0`, framebuffer ioctls, a shared `mmap`, and `KDSETMODE`
5
+ through Bun FFI; it does not require X11, Wayland, SDL, or a window manager.
6
+
7
+ ## Demo
8
+
9
+ Run it on Buninu's physical Linux console:
10
+
11
+ ```sh
12
+ bun /lib/fbdev.js
13
+ ```
14
+
15
+ The demo switches the active console to graphics mode, clears the framebuffer,
16
+ draws a cyan triangle, and waits. Press any key to restore text mode and exit.
17
+ Ordinary failures, direct process exit, `SIGHUP`, `SIGINT`, `SIGQUIT`, and
18
+ `SIGTERM` also restore the prior keyboard raw state and attempt to return the
19
+ console to text mode.
20
+
21
+ Each repeatable `-p`/`--points` supplies one polygon as x,y coordinate pairs:
22
+
23
+ ```sh
24
+ bun /lib/fbdev.js -p '100,50 40,250 300,250'
25
+ bun /lib/fbdev.js \
26
+ -p '100,50 40,250 300,250' \
27
+ -p '400,80 350,220 450,220'
28
+ ```
29
+
30
+ Every occurrence draws a separate filled polygon. Comma and whitespace
31
+ separators may be mixed inside its quoted value; the clearest form uses a
32
+ comma within each `x,y` point and spaces between points. If one polygon
33
+ contains an odd number of numeric values, its final unpaired value is ignored.
34
+ A `-p` containing only one number is therefore ignored as a whole. Each
35
+ non-empty polygon needs at least three complete points; more than three points
36
+ remain one polygon rather than starting another. If no drawable `-p` remains,
37
+ the demo draws its default triangle.
38
+
39
+ ## JavaScript API
40
+
41
+ ```js
42
+ import {
43
+ openFramebuffer,
44
+ clear,
45
+ drawPoint,
46
+ drawLine,
47
+ drawLines,
48
+ fillPolygon,
49
+ flush,
50
+ } from "/lib/fbdev.js";
51
+
52
+ const display = openFramebuffer();
53
+ try {
54
+ clear(display);
55
+ fillPolygon(display, [[100, 50], [40, 250], [300, 250]], {
56
+ r: 64, g: 200, b: 255, a: 255,
57
+ });
58
+ drawLine(display, 0, 0, display.width - 1, display.height - 1, {
59
+ r: 255, g: 255, b: 255,
60
+ });
61
+ flush(display);
62
+ } finally {
63
+ display.close();
64
+ }
65
+ ```
66
+
67
+ The API uses short drawing names because it operates on a framebuffer rather
68
+ than an X11 `Display`. Colours are `{ r, g, b, a }` objects.
69
+
70
+ ### Export reference
71
+
72
+ | Export | Purpose |
73
+ | --- | --- |
74
+ | `Framebuffer`, `openFramebuffer(path?)` | Open and describe a packed-pixel framebuffer. |
75
+ | `normalizePoints(values)` | Accept flat numbers, `[x,y]` arrays, or `{x,y}` objects. An unpaired flat value is ignored. |
76
+ | `parsePoints(strings)` | Parse comma/whitespace-separated CLI-style coordinates. |
77
+ | `drawPoint(display, x, y, color?)` | Draw one clipped pixel. |
78
+ | `drawLine(display, x1, y1, x2, y2, color?)` | Draw a clipped Bresenham line. |
79
+ | `drawLines(display, points, color?, close?)` | Join a sequence of points. |
80
+ | `fillPolygon(display, points, color?)` | Fill a polygon with the even-odd scanline rule. |
81
+ | `clear(display, color?)` | Fill the in-memory frame, black by default. |
82
+ | `flush(display)` | Copy the in-memory frame to the shared `/dev/fb0` mapping and synchronize it. |
83
+ | `blitImageData(display, imageData, x?, y?)` | Copy an RGBA `ImageData` (for example from CanvasKit's HTML canvas emulation) into the frame, converting to the framebuffer's format and clipping. |
84
+ | `openConsole`, `setConsoleGraphics`, `setConsoleText` | Explicit Linux virtual-console control for callers that need it. |
85
+ | `activeConsole(console)`, `activateConsole(console, n)`, `consoleNumber(path)` | `VT_GETSTATE` / `VT_ACTIVATE` + `VT_WAITACTIVE`, and the number in a `/dev/ttyN` path. |
86
+ | `watchConsoleSwitches(console, onAcquire)` | `VT_SETMODE` with `VT_PROCESS`: answers the kernel's switch-away and switch-back signals and calls `onAcquire` when the display returns. Returns a function restoring automatic switching. |
87
+ | `runGraphics(draw, { device, console, switchConsole }?)` | Open the framebuffer, put the console in graphics mode, run `await draw(display, { console, onRestore, onAcquire })`, and restore text mode however the call ends (return, throw, `process.exit()`, `SIGHUP`/`SIGINT`/`SIGQUIT`/`SIGTERM`). `console` lists console devices to try, such as `["/dev/tty1"]`; when the one opened is a `/dev/ttyN` other than the console on screen, the display is switched to it first and back afterwards (`switchConsole: false` disables this). Console switches with Ctrl-Alt-Fn keep working; `onAcquire(callback)` runs when the display comes back so the caller can repaint, and `onRestore(callback)` registers cleanup to run before text mode returns. |
88
+ | `runDemo({ polygons, device }?)` | Run the CLI demo through `runGraphics()`. Each item in `polygons` is drawn separately. |
89
+
90
+ The module supports 16-, 24-, and 32-bit packed-pixel framebuffers and uses
91
+ the kernel-reported stride, offsets, and RGB bitfields. Paletted and unusual
92
+ non-packed framebuffer formats are rejected rather than drawn incorrectly.
93
+
94
+ ## Operational limits
95
+
96
+ - Run the demo from a real Linux virtual console. A PTY, serial console, SSH
97
+ session, or terminal pane cannot accept `KDSETMODE`.
98
+ - The framebuffer device and active virtual console must be accessible to the
99
+ process; Buninu normally runs as root.
100
+ - Imported drawing calls modify an in-memory frame until `flush()` is called.
101
+ - Importing the module does not switch console mode. Only `runDemo()` or an
102
+ explicit `setConsoleGraphics()` call does so.
103
+ - No userspace cleanup can run after `SIGKILL`, a kernel panic, power loss, or
104
+ a native runtime crash. If one leaves the virtual console in graphics mode,
105
+ switch to another VT or reboot to recover it.
106
+
107
+ ## See also
108
+
109
+ - Source: `/lib/fbdev.js`
110
+ - Shared native binding: `/lib/dlopen.js`
111
+ - Skia on this framebuffer: `/lib/canvas.js` (`canvas.md`); the terminal
112
+ built on both: `bunterm`
113
+ - Linux UAPI: `linux/fb.h` and `linux/kd.h`
@@ -27,7 +27,7 @@ Use `-` as ARCHIVE to read from standard input or write to standard output.
27
27
  | `-C DIR`, `--directory=DIR` | Read input files from DIR, or extract into DIR. |
28
28
  | `-z`, `--gzip` | Create gzip-compressed tar, or explicitly identify gzip input. |
29
29
  | `--zstd` | Create or read a zstd-compressed tar. A `.zst` or `.tzst` input is also detected by name. |
30
- | `-v` | Print names while creating/extracting; with `-t`, also show each regular file's size and modification time. |
30
+ | `-v` | Print names while creating/extracting; with `-t`, also show each member's size, modification time, kind and link target. |
31
31
 
32
32
  Short options can be combined, for example `-xzvf` or `-czf`.
33
33
  For the traditional first option word, the leading hyphen is optional, so
@@ -42,8 +42,34 @@ tar -xzf source.tar.gz -C /tmp/source
42
42
  tar -tf source.tar.gz
43
43
  tar --zstd -cf backup.tar.zst home
44
44
  tar --zstd -xf backup.tar.zst -C /mnt/restore
45
+
46
+ # An Alpine root filesystem, symbolic links and all; then enter it
47
+ tar -xzf alpine-minirootfs-3.24.1-x86_64.tar.gz -C /mnt/alpine
48
+ chroot /mnt/alpine
45
49
  ```
46
50
 
51
+ ## Links
52
+
53
+ Extraction restores symbolic links exactly as the archive records them,
54
+ including absolute targets such as `/bin/busybox` and targets that do not
55
+ exist yet, and it restores hard links. This is what GNU tar does, and it is
56
+ what an unpacked root filesystem needs: an Alpine minirootfs is 335 symbolic
57
+ links, 306 of them absolute, and without them the result has no `/bin/sh`.
58
+
59
+ `Bun.Archive` creates only the symbolic links whose target is relative and
60
+ stays inside the extraction directory, and skips hard links entirely, so
61
+ this command reads the archive's own headers (ustar, GNU `L`/`K` long names,
62
+ pax `x` records) and creates the rest itself once every file is in place.
63
+ Nothing is ever written through a link, because the links are made last.
64
+
65
+ A member's own name still follows GNU tar's rule: a leading `/` is dropped
66
+ and a name containing `..` is refused, so an archive cannot write outside
67
+ the extraction directory.
68
+
69
+ The same header scan backs listing, so `-t` reports every member —
70
+ directories, links, devices — and not just regular files. A symbolic link
71
+ shows its target, a hard link the file it points at.
72
+
47
73
  ## Current Bun.Archive limits
48
74
 
49
75
  Creation stores regular files only. Symbolic links are rejected rather than
@@ -51,11 +77,9 @@ silently archived as the wrong type; modes, ownership, hard links, and empty
51
77
  directories are not preserved. Bun 1.4.3 also requires file contents to be
52
78
  materialized in memory before archive creation.
53
79
 
54
- Extraction restores directories and symbolic links, but Bun 1.4.3 skips hard
55
- links. Listing and verbose extraction use `Archive.files()`, so they report
56
- regular files only. Verbose listing can show their size and modification time,
57
- but not the unavailable mode or owner. Bun provides gzip and zstd codecs but no xz or bzip2 codec,
58
- so this command does not accept `.tar.xz` or `.tar.bz2`.
80
+ Extraction does not restore modes, ownership or timestamps. Bun provides gzip
81
+ and zstd codecs but no xz or bzip2 codec, so this command does not accept
82
+ `.tar.xz` or `.tar.bz2`.
59
83
 
60
84
  Appending with `-r`/`rvf` is not supported. `Bun.Archive` has no append API;
61
85
  rebuilding an existing archive through `Archive.files()` would silently lose