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.
- package/LICENSES/Apache-2.0.txt +202 -0
- package/LICENSES/BSD-3-Clause-Skia.txt +29 -0
- package/LICENSES/Bitstream-Vera.txt +44 -0
- package/LICENSES/MIT-xterm.js.txt +21 -0
- package/LICENSES/OFL-1.1.txt +91 -0
- package/NOTICE.md +18 -1
- package/README.md +101 -9
- package/fetch-alpine.sh +6 -0
- package/graphics.md +218 -0
- package/initramfs/bin/bunterm +70 -0
- package/initramfs/bin/chroot +213 -0
- package/initramfs/bin/stripansi +27 -0
- package/initramfs/bin/tar +189 -14
- package/initramfs/buninu/CHANGELOG.md +40 -0
- package/initramfs/buninu/apps/jsgotty/gotty.js +4 -3
- package/initramfs/buninu/apps/jsgotty/package.json +1 -1
- package/initramfs/buninu/package.json +1 -1
- package/initramfs/init.js +80 -1
- package/initramfs/lib/bunterm/glyphs.js +446 -0
- package/initramfs/lib/bunterm/images.js +225 -0
- package/initramfs/lib/bunterm/input.js +140 -0
- package/initramfs/lib/bunterm/mouse.js +222 -0
- package/initramfs/lib/bunterm/renderer.js +469 -0
- package/initramfs/lib/bunterm/terminal.js +324 -0
- package/initramfs/lib/canvas.js +181 -0
- package/initramfs/lib/canvaskit/LICENSE +29 -0
- package/initramfs/lib/canvaskit/canvaskit.js +264 -0
- package/initramfs/lib/canvaskit/canvaskit.wasm +0 -0
- package/initramfs/lib/dlopen.js +18 -2
- package/initramfs/lib/document.js +5 -1
- package/initramfs/lib/fbdev.js +581 -0
- package/initramfs/lib/xterm/LICENSE +21 -0
- package/initramfs/lib/xterm/addon-unicode-graphemes.mjs +44 -0
- package/initramfs/lib/xterm/xterm-headless.mjs +46 -0
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +112 -0
- package/initramfs/usr/share/doc/buninu-linux/canvas.md +91 -0
- package/initramfs/usr/share/doc/buninu-linux/chroot.md +105 -0
- package/initramfs/usr/share/doc/buninu-linux/fbdev.md +113 -0
- package/initramfs/usr/share/doc/buninu-linux/tar.md +30 -6
- package/initramfs/usr/share/fonts/DejaVuSansMono-Bold.ttf +0 -0
- package/initramfs/usr/share/fonts/DejaVuSansMono.ttf +0 -0
- package/initramfs/usr/share/fonts/LICENSE-Apache-2.0.txt +202 -0
- package/initramfs/usr/share/fonts/LICENSE-DejaVu.txt +44 -0
- package/initramfs/usr/share/fonts/LICENSE-OFL-1.1.txt +91 -0
- package/initramfs/usr/share/fonts/NotoColorEmoji.ttf +0 -0
- package/initramfs/usr/share/fonts/NotoColorEmojiFlags.ttf +0 -0
- package/initramfs/usr/share/fonts/NotoSansCJK-Regular.ttc +0 -0
- package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted.ttf +0 -0
- package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted2.ttf +0 -0
- package/initramfs/usr/share/fonts/Roboto-Regular.ttf +0 -0
- package/initramfs/usr/share/fonts/fonts.json +28 -0
- package/package.json +2 -2
- package/run-qemu.sh +1 -1
package/initramfs/bin/tar
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// tar — a small tar/gzip/zstd CLI over Bun.Archive.
|
|
3
3
|
|
|
4
4
|
import { lstat, readdir } from "node:fs/promises";
|
|
5
|
-
import {
|
|
5
|
+
import { linkSync, mkdirSync, rmSync, symlinkSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
6
7
|
import { showDocument } from "../lib/document.js";
|
|
7
8
|
|
|
8
9
|
const fail = (message) => {
|
|
@@ -89,16 +90,186 @@ const archiveBytes = async () => {
|
|
|
89
90
|
return zstd ? Bun.zstdDecompressSync(bytes) : bytes;
|
|
90
91
|
};
|
|
91
92
|
|
|
93
|
+
// ----------------------------------------------------------- tar headers --
|
|
94
|
+
//
|
|
95
|
+
// Bun.Archive extracts files, and symbolic links only when their target is
|
|
96
|
+
// relative and stays inside the extraction directory, which drops every
|
|
97
|
+
// absolute target (`/bin/busybox` — 306 of the 335 links in an Alpine
|
|
98
|
+
// minirootfs), every hard link, and every non-regular entry in
|
|
99
|
+
// `Archive.files()`. The header scan below reads the archive itself, so
|
|
100
|
+
// listing is complete and the links can be restored afterwards. Formats:
|
|
101
|
+
// POSIX ustar, GNU long names (`L`/`K`) and pax extended headers (`x`).
|
|
102
|
+
|
|
103
|
+
const BLOCK = 512;
|
|
104
|
+
const decoder = new TextDecoder();
|
|
105
|
+
|
|
106
|
+
const text = (block, offset, length) => {
|
|
107
|
+
let end = offset;
|
|
108
|
+
const stop = offset + length;
|
|
109
|
+
while (end < stop && block[end] !== 0) end++;
|
|
110
|
+
return decoder.decode(block.subarray(offset, end));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const octal = (block, offset, length) => {
|
|
114
|
+
// GNU base-256 encoding for values that do not fit the octal field.
|
|
115
|
+
if (block[offset] & 0x80) {
|
|
116
|
+
let value = block[offset] & 0x7f;
|
|
117
|
+
for (let index = offset + 1; index < offset + length; index++) value = value * 256 + block[index];
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
const digits = text(block, offset, length).trim();
|
|
121
|
+
const value = Number.parseInt(digits, 8);
|
|
122
|
+
return Number.isFinite(value) ? value : 0;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// The header checksum is computed with its own field read as spaces. A
|
|
126
|
+
// mismatch means this is not a tar header, so scanning stops there.
|
|
127
|
+
const checksumMatches = (block) => {
|
|
128
|
+
let sum = 0;
|
|
129
|
+
for (let index = 0; index < BLOCK; index++) {
|
|
130
|
+
sum += index >= 148 && index < 156 ? 0x20 : block[index];
|
|
131
|
+
}
|
|
132
|
+
return sum === octal(block, 148, 8);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const paxRecords = (data) => {
|
|
136
|
+
const records = {};
|
|
137
|
+
let offset = 0;
|
|
138
|
+
const content = decoder.decode(data);
|
|
139
|
+
while (offset < content.length) {
|
|
140
|
+
const space = content.indexOf(" ", offset);
|
|
141
|
+
if (space === -1) break;
|
|
142
|
+
const length = Number.parseInt(content.slice(offset, space), 10);
|
|
143
|
+
if (!Number.isFinite(length) || length <= 0) break;
|
|
144
|
+
const record = content.slice(space + 1, offset + length).replace(/\n$/, "");
|
|
145
|
+
const equals = record.indexOf("=");
|
|
146
|
+
if (equals > 0) records[record.slice(0, equals)] = record.slice(equals + 1);
|
|
147
|
+
offset += length;
|
|
148
|
+
}
|
|
149
|
+
return records;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const entryKinds = {
|
|
153
|
+
"0": "file", "\0": "file", "1": "hardlink", "2": "symlink", "3": "character",
|
|
154
|
+
"4": "block", "5": "directory", "6": "fifo", "7": "file",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Every member of the archive, in order, as { name, kind, target, size, mode,
|
|
158
|
+
// mtime }. Returns null when the bytes do not parse as tar.
|
|
159
|
+
const scanEntries = (bytes) => {
|
|
160
|
+
const entries = [];
|
|
161
|
+
let offset = 0;
|
|
162
|
+
let longName = null;
|
|
163
|
+
let longTarget = null;
|
|
164
|
+
let extended = {};
|
|
165
|
+
while (offset + BLOCK <= bytes.length) {
|
|
166
|
+
const block = bytes.subarray(offset, offset + BLOCK);
|
|
167
|
+
offset += BLOCK;
|
|
168
|
+
if (block.every((byte) => byte === 0)) break; // end-of-archive blocks
|
|
169
|
+
if (!checksumMatches(block)) return entries.length ? entries : null;
|
|
170
|
+
const flag = String.fromCharCode(block[156] || 0x30);
|
|
171
|
+
const size = extended.size !== undefined ? Number(extended.size) : octal(block, 124, 12);
|
|
172
|
+
const dataBlocks = Math.ceil(size / BLOCK) * BLOCK;
|
|
173
|
+
const data = bytes.subarray(offset, offset + size);
|
|
174
|
+
if (flag === "L" || flag === "K") {
|
|
175
|
+
const value = decoder.decode(data).replace(/\0+$/, "");
|
|
176
|
+
if (flag === "L") longName = value; else longTarget = value;
|
|
177
|
+
offset += dataBlocks;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (flag === "x" || flag === "X") {
|
|
181
|
+
extended = { ...extended, ...paxRecords(data) };
|
|
182
|
+
offset += dataBlocks;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (flag === "g") {
|
|
186
|
+
offset += dataBlocks;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const prefix = text(block, 345, 155);
|
|
190
|
+
const stored = text(block, 0, 100);
|
|
191
|
+
const name = extended.path ?? longName ?? (prefix ? `${prefix}/${stored}` : stored);
|
|
192
|
+
const target = extended.linkpath ?? longTarget ?? text(block, 157, 100);
|
|
193
|
+
entries.push({
|
|
194
|
+
name: name.replace(/^\.\//, ""),
|
|
195
|
+
kind: entryKinds[flag] ?? "file",
|
|
196
|
+
target,
|
|
197
|
+
size,
|
|
198
|
+
mode: octal(block, 100, 8) & 0o7777,
|
|
199
|
+
mtime: octal(block, 136, 12) * 1000,
|
|
200
|
+
});
|
|
201
|
+
longName = null;
|
|
202
|
+
longTarget = null;
|
|
203
|
+
extended = {};
|
|
204
|
+
offset += dataBlocks;
|
|
205
|
+
}
|
|
206
|
+
return entries;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const tarBytes = (bytes) => {
|
|
210
|
+
if (bytes[0] === 0x1f && bytes[1] === 0x8b) return Bun.gunzipSync(bytes);
|
|
211
|
+
if (bytes[0] === 0x28 && bytes[1] === 0xb5 && bytes[2] === 0x2f && bytes[3] === 0xfd) {
|
|
212
|
+
return Bun.zstdDecompressSync(bytes);
|
|
213
|
+
}
|
|
214
|
+
return bytes;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Where a member lands, following GNU tar: a leading `/` is stripped, and a
|
|
218
|
+
// name that climbs out with `..` is refused. A link's target is never
|
|
219
|
+
// examined — an absolute symbolic link is a normal thing for a root
|
|
220
|
+
// filesystem to contain — and since links are made after every file, nothing
|
|
221
|
+
// is ever written through one.
|
|
222
|
+
const memberName = (name) => {
|
|
223
|
+
const stripped = name.replace(/^\/+/, "");
|
|
224
|
+
const parts = stripped.split("/");
|
|
225
|
+
if (parts.includes("..")) return null;
|
|
226
|
+
return stripped || null;
|
|
227
|
+
};
|
|
228
|
+
|
|
92
229
|
const patterns = () => options.operands.flatMap((name) => {
|
|
93
230
|
const clean = name.replace(/^\.\//, "").replace(/\/$/, "");
|
|
94
231
|
return [clean, `${clean}/**`];
|
|
95
232
|
});
|
|
96
233
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
234
|
+
const kindMarks = { directory: "/", symlink: "@", fifo: "|", character: "#", block: "#" };
|
|
235
|
+
|
|
236
|
+
const verboseListing = (entry) => {
|
|
237
|
+
const modified = entry.mtime > 0
|
|
238
|
+
? new Date(entry.mtime).toISOString().replace("T", " ").slice(0, 16)
|
|
100
239
|
: "---- -- -- --:--";
|
|
101
|
-
|
|
240
|
+
const suffix = entry.kind === "symlink" ? ` -> ${entry.target}`
|
|
241
|
+
: entry.kind === "hardlink" ? ` link to ${entry.target}`
|
|
242
|
+
: "";
|
|
243
|
+
return `${String(entry.size).padStart(12)} ${modified} ${entry.name}${kindMarks[entry.kind] ?? ""}${suffix}`;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Creates the links Bun.Archive left out. Files are already in place, so a
|
|
247
|
+
// hard link finds its target and a symbolic link cannot be written through.
|
|
248
|
+
const restoreLinks = (entries) => {
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
if (entry.kind !== "symlink" && entry.kind !== "hardlink") continue;
|
|
251
|
+
const name = memberName(entry.name);
|
|
252
|
+
if (!name || !entry.target) continue;
|
|
253
|
+
const path = resolve(options.directory, name);
|
|
254
|
+
try {
|
|
255
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
256
|
+
try {
|
|
257
|
+
if (entry.kind === "symlink") symlinkSync(entry.target, path);
|
|
258
|
+
else linkSync(resolve(options.directory, memberName(entry.target) ?? entry.target), path);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error.code !== "EEXIST") throw error;
|
|
261
|
+
// GNU tar replaces what is in the way rather than following it.
|
|
262
|
+
rmSync(path, { force: true });
|
|
263
|
+
if (entry.kind === "symlink") symlinkSync(entry.target, path);
|
|
264
|
+
else linkSync(resolve(options.directory, memberName(entry.target) ?? entry.target), path);
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
// A hard link whose target was filtered out by a member pattern is
|
|
268
|
+
// expected; anything else is worth reporting without stopping.
|
|
269
|
+
if (entry.kind === "hardlink" && error.code === "ENOENT" && options.operands.length) continue;
|
|
270
|
+
console.error(`tar: ${entry.name}: ${error.message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
102
273
|
};
|
|
103
274
|
|
|
104
275
|
if (options.operation === "x" || options.operation === "t") {
|
|
@@ -109,22 +280,26 @@ if (options.operation === "x" || options.operation === "t") {
|
|
|
109
280
|
fail(error?.message || String(error));
|
|
110
281
|
}
|
|
111
282
|
|
|
283
|
+
// The header scan is what makes listing and links complete; if the bytes
|
|
284
|
+
// do not parse as tar, fall back to what Bun.Archive reports.
|
|
285
|
+
const scanned = scanEntries(tarBytes(await archiveBytes()));
|
|
286
|
+
const wanted = options.operands.length ? patterns().map((pattern) => new Bun.Glob(pattern)) : null;
|
|
287
|
+
const members = scanned
|
|
288
|
+
? scanned.filter((entry) => !wanted || wanted.some((glob) => glob.match(entry.name)))
|
|
289
|
+
: [...(await archive.files(options.operands.length ? patterns() : undefined))]
|
|
290
|
+
.map(([name, file]) => ({ name, kind: "file", target: "", size: file.size, mode: 0o644, mtime: file.lastModified }));
|
|
291
|
+
|
|
112
292
|
if (options.operation === "t") {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (const [name, file] of files) {
|
|
116
|
-
console.log(options.verbose ? verboseListing(name, file) : name);
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
fail(error?.message || String(error));
|
|
293
|
+
for (const entry of members) {
|
|
294
|
+
console.log(options.verbose ? verboseListing(entry) : entry.name);
|
|
120
295
|
}
|
|
121
296
|
} else {
|
|
122
297
|
try {
|
|
123
298
|
if (options.verbose) {
|
|
124
|
-
const
|
|
125
|
-
for (const name of files.keys()) console.log(name);
|
|
299
|
+
for (const entry of members) console.log(entry.name);
|
|
126
300
|
}
|
|
127
301
|
await archive.extract(options.directory, options.operands.length ? { glob: patterns() } : undefined);
|
|
302
|
+
restoreLinks(members);
|
|
128
303
|
} catch (error) {
|
|
129
304
|
fail(error?.message || String(error));
|
|
130
305
|
}
|
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.15 - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Sync jsgotty to 1.1.12
|
|
8
|
+
* Export `BunPtyBackend` from `gotty.js`, the PTY layer built on
|
|
9
|
+
`Bun.spawn({ terminal })`, so other programs can run a command on a
|
|
10
|
+
pseudo-terminal without their own FFI. Buninu Linux's `bunterm`, a
|
|
11
|
+
graphical terminal on the framebuffer, is its first user, together with
|
|
12
|
+
the already exported `KittyGraphicsParser`
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- jsgotty: a chunked kitty graphics transfer whose first chunk names an
|
|
17
|
+
image id (`i=`) is now continued by later chunks that carry only `m=`, as
|
|
18
|
+
the protocol allows. Previously those chunks were treated as new anonymous
|
|
19
|
+
transfers, so the image never completed. jsgotty's own `showimg` repeats
|
|
20
|
+
the id in every chunk and was unaffected; `kitten icat`, timg, chafa and
|
|
21
|
+
similar clients were
|
|
22
|
+
|
|
23
|
+
## 0.4.13 - 2026-09-22
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Sync bunmsh to 0.3.9
|
|
28
|
+
* `Ctrl-U` removes and saves the text before the cursor; pressing it again
|
|
29
|
+
at the beginning of the line restores it. `Ctrl-K` independently toggles
|
|
30
|
+
the text after the cursor when at the end of the line
|
|
31
|
+
* Unquoted pathname patterns follow directory symbolic links used as
|
|
32
|
+
intermediate path components, as traditional shells do, so patterns such
|
|
33
|
+
as `/sys/class/net/*/device` pass through sysfs class links
|
|
34
|
+
* The fallback `ls`/`lsfancy` lists a glob's files as one listing instead
|
|
35
|
+
of one section per file, honors `--`, and follows a command-line symlink
|
|
36
|
+
to a directory
|
|
37
|
+
- Sync jsmdcui to 0.19.2
|
|
38
|
+
* Pressing `Ctrl-W` twice quickly switches to the next tab like `Alt-T`
|
|
39
|
+
from either an editor or a terminal pane; at the rightmost tab, when every
|
|
40
|
+
tab holds only terminal panes, it opens a new editor tab instead of
|
|
41
|
+
cycling back
|
|
42
|
+
|
|
3
43
|
## 0.4.12 - 2026-09-20
|
|
4
44
|
|
|
5
45
|
### Fixed
|
|
@@ -1173,9 +1173,9 @@ class KittyGraphicsParser {
|
|
|
1173
1173
|
|
|
1174
1174
|
if (control.m === "1") {
|
|
1175
1175
|
this.transfers.set(key, existing);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1176
|
+
// Later chunks of a transfer only need m=; they continue the transfer
|
|
1177
|
+
// in progress whether or not its first chunk named an image id.
|
|
1178
|
+
this.implicitTransferKey = key;
|
|
1179
1179
|
return null;
|
|
1180
1180
|
}
|
|
1181
1181
|
|
|
@@ -3314,6 +3314,7 @@ async function bootstrap() {
|
|
|
3314
3314
|
|
|
3315
3315
|
module.exports.CursorStateTracker = CursorStateTracker;
|
|
3316
3316
|
module.exports.KittyGraphicsParser = KittyGraphicsParser;
|
|
3317
|
+
module.exports.BunPtyBackend = BunPtyBackend;
|
|
3317
3318
|
module.exports.webSocketOriginAllowed = webSocketOriginAllowed;
|
|
3318
3319
|
|
|
3319
3320
|
if (require.main === module) {
|
package/initramfs/init.js
CHANGED
|
@@ -94,6 +94,79 @@ const mountFilesystems = () => {
|
|
|
94
94
|
mountFilesystems();
|
|
95
95
|
console.log("init.js: mounted /proc, /sys, /dev, /tmp, /dev/pts");
|
|
96
96
|
|
|
97
|
+
// Num Lock. The keypad's lock state lives per virtual console in the
|
|
98
|
+
// kernel's keyboard table, and the kernel starts every console with it off,
|
|
99
|
+
// so the keypad types arrows instead of digits. KDSKBLED carries the current
|
|
100
|
+
// flags in its low nibble and the ones a console is reset to in its high
|
|
101
|
+
// nibble; KDSETLED with a value above 7 puts the physical LED back to
|
|
102
|
+
// following those flags. A console allocated later (Ctrl-Alt-F3 for the
|
|
103
|
+
// first time) starts from the kernel default again, so numlock() is left in
|
|
104
|
+
// the REPL for that case.
|
|
105
|
+
const KDSETLED = 0x4b32;
|
|
106
|
+
const KDGKBLED = 0x4b64;
|
|
107
|
+
const KDSKBLED = 0x4b65;
|
|
108
|
+
const LED_NUM = 0x02;
|
|
109
|
+
|
|
110
|
+
// Nothing here is essential to booting, so a console that is not a virtual
|
|
111
|
+
// terminal, a missing device node or a rejected ioctl is skipped rather than
|
|
112
|
+
// reported: the remaining consoles are still set, and the system comes up
|
|
113
|
+
// either way.
|
|
114
|
+
const setNumLock = (enabled = true) => {
|
|
115
|
+
const consoles = [];
|
|
116
|
+
const paths = ["/dev/tty0", "/dev/console", "/dev/tty"];
|
|
117
|
+
for (let index = 1; index <= 12; index++) paths.push(`/dev/tty${index}`);
|
|
118
|
+
for (const path of paths) {
|
|
119
|
+
let fd = -1;
|
|
120
|
+
try {
|
|
121
|
+
fd = libc.symbols.open(cString(path), 2, 0);
|
|
122
|
+
if (fd < 0) continue;
|
|
123
|
+
const state = new Uint8Array(1);
|
|
124
|
+
if (libc.symbols.ioctl(fd, KDGKBLED, ptr(state)) !== 0) continue;
|
|
125
|
+
const current = enabled ? state[0] | LED_NUM : state[0] & ~LED_NUM;
|
|
126
|
+
const fallback = enabled ? (state[0] >> 4) | LED_NUM : (state[0] >> 4) & ~LED_NUM;
|
|
127
|
+
const flags = (current & 7) | ((fallback & 7) << 4);
|
|
128
|
+
if (libc.symbols.ioctl(fd, KDSKBLED, flags) !== 0) continue;
|
|
129
|
+
libc.symbols.ioctl(fd, KDSETLED, 0x80);
|
|
130
|
+
consoles.push(path);
|
|
131
|
+
} catch {
|
|
132
|
+
// This console keeps whatever state it had; the others still get set.
|
|
133
|
+
} finally {
|
|
134
|
+
if (fd >= 0) {
|
|
135
|
+
try { libc.symbols.close(fd); } catch {}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return consoles;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
globalThis.numlock = setNumLock;
|
|
143
|
+
try {
|
|
144
|
+
const consoles = setNumLock(true);
|
|
145
|
+
console.log(consoles.length
|
|
146
|
+
? `init.js: num lock on (${consoles.join(" ")})`
|
|
147
|
+
: "init.js: no virtual console accepted a num lock setting");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`init.js: num lock: ${error?.message ?? error}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The input layer's character devices, so a pointing device has a
|
|
153
|
+
// /dev/input/event* node for `bunterm --mouse` to read. Loading them only
|
|
154
|
+
// creates those nodes: the console keyboard is unaffected, and nothing reads
|
|
155
|
+
// a pointer unless it is asked to. A USB mouse also needs the usbhid stack,
|
|
156
|
+
// which `cfg.all` or a --real boot brings in.
|
|
157
|
+
try {
|
|
158
|
+
const { tryModprobe } = await import("/lib/modprobe.js");
|
|
159
|
+
const loaded = ["evdev", "psmouse"].filter((module) => tryModprobe(module) !== null);
|
|
160
|
+
const { readdirSync } = await import("node:fs");
|
|
161
|
+
const devices = (() => {
|
|
162
|
+
try { return readdirSync("/dev/input").filter((name) => name.startsWith("event")); } catch { return []; }
|
|
163
|
+
})();
|
|
164
|
+
console.log(`init.js: input modules ${loaded.length ? loaded.join(" ") : "none"}`
|
|
165
|
+
+ ` (${devices.length} event device${devices.length === 1 ? "" : "s"})`);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`init.js: input modules: ${error?.message ?? error}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
97
170
|
const { default: repl } = await import("node:repl");
|
|
98
171
|
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
99
172
|
|
|
@@ -341,6 +414,12 @@ const launchBunmsh = () => {
|
|
|
341
414
|
|
|
342
415
|
const launchBuninu = () => {
|
|
343
416
|
|
|
417
|
+
console.log(`
|
|
418
|
+
Type bunterm in bunmsh: Graphical Terminal
|
|
419
|
+
Shows Emojis, CJK chars
|
|
420
|
+
Supports Kitty Graphics Protocol
|
|
421
|
+
`)
|
|
422
|
+
|
|
344
423
|
const entry = `/buninu/bin/init.js`;
|
|
345
424
|
console.log(`buninu: Starting ${entry} --local`);
|
|
346
425
|
const shell = Bun.spawnSync([
|
|
@@ -348,7 +427,7 @@ const launchBuninu = () => {
|
|
|
348
427
|
], {
|
|
349
428
|
env: {
|
|
350
429
|
...process.env,
|
|
351
|
-
PATH:"/bin:/usr/bin:/buninu/bin",
|
|
430
|
+
PATH:"/bin:/sbin:/usr/bin:/usr/sbin:/buninu/.bun/bin:/buninu/bin",
|
|
352
431
|
HOME:"/buninu"
|
|
353
432
|
},
|
|
354
433
|
stdin: "inherit",
|