buninu-linux 0.7.0 → 0.7.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/README.md +74 -10
- package/fetch-alpine.sh +16 -0
- package/graphics.md +23 -7
- package/initramfs/bin/bunterm +3 -1
- package/initramfs/init.js +26 -0
- package/initramfs/lib/bunterm/clipboard.js +51 -0
- package/initramfs/lib/bunterm/terminal.js +5 -0
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
* `start` starts the Buninu userspace shell
|
|
37
37
|
* `cfg.disk` + shell `mount` mounts local disks
|
|
38
38
|
* `cfg.net` loads common wired NIC and Android USB-tethering drivers
|
|
39
|
+
* `cfg.sound` loads every packaged ALSA sound module
|
|
39
40
|
* Android phone USB tethering over RNDIS has been tested successfully, allowing Buninu Linux to access the Internet through an Android phone
|
|
40
41
|
* IP addresses and routes are configured manually because the image does not yet include a DHCP client.
|
|
41
42
|
* The bundled `jmi` editor, jsmdcui App runtime, and local JavaScript execution also work.
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
+ Also has mouse click, wheel, and cursor drawing
|
|
46
47
|
+ Simplified browser works: @sanohiro/casty in chroot debian
|
|
47
48
|
+ (Use at your own risk: We haven't fully examined its code)
|
|
49
|
+
+ Playing YouTube with sound(experimental)
|
|
48
50
|
+ See [Commands inside `/bin`](#commands-inside-bin) for browser usage instructions
|
|
49
51
|
|
|
50
52
|
---
|
|
@@ -142,7 +144,7 @@ display and USB keyboard with a welcome ending like this:
|
|
|
142
144
|
Welcome to Buninu Linux!
|
|
143
145
|
Bun 1.4.2 is now PID 1
|
|
144
146
|
Type start() to run buninu --local
|
|
145
|
-
Configuration getters: cfg.all, cfg.disk, cfg.net, cfg.power
|
|
147
|
+
Configuration getters: cfg.all, cfg.disk, cfg.net, cfg.power, cfg.sound
|
|
146
148
|
|
|
147
149
|
bun-repl>
|
|
148
150
|
```
|
|
@@ -157,6 +159,7 @@ Load only the subsystem needed, then enter the Buninu shell:
|
|
|
157
159
|
cfg.net // wired NIC and Android USB-tethering drivers
|
|
158
160
|
cfg.disk // SATA/NVMe/USB storage drivers and detected block devices
|
|
159
161
|
cfg.power // battery, AC, button and thermal drivers
|
|
162
|
+
cfg.sound // all packaged ALSA sound modules and detected sound devices
|
|
160
163
|
start()
|
|
161
164
|
```
|
|
162
165
|
|
|
@@ -208,6 +211,8 @@ bun ./index.js -fbr
|
|
|
208
211
|
The first boot line from `run-qemu.sh` names the guest, machine, accelerator
|
|
209
212
|
and firmware it picked. A bare `-r` boots whichever architecture `vda.img`
|
|
210
213
|
was last built for; `-br --arch arm64` is the aarch64 edit-and-boot loop.
|
|
214
|
+
To run `bunterm` in a QEMU window on the aarch64 guest, see
|
|
215
|
+
[What differs on aarch64](#what-differs-on-aarch64).
|
|
211
216
|
|
|
212
217
|
#### amd64 Linux
|
|
213
218
|
|
|
@@ -231,9 +236,32 @@ qemu-efi-aarch64` to run.
|
|
|
231
236
|
Alpine's aarch64 packages and `bun-linux-aarch64-musl`.
|
|
232
237
|
* The serial console is `ttyAMA0` (QEMU `virt`'s PL011) instead of `ttyS0`.
|
|
233
238
|
* `--real` is x86_64 only: its module set is PC hardware.
|
|
234
|
-
*
|
|
235
|
-
|
|
236
|
-
|
|
239
|
+
* QEMU's `virt` machine has no display or PS/2 devices, so by default
|
|
240
|
+
`bunterm` has nothing to draw on; the serial REPL and the Buninu shell work
|
|
241
|
+
as on x86_64. To run `bunterm`, build with `--linux-lts` and give QEMU a
|
|
242
|
+
`ramfb` display and virtio input devices. On a Mac, `-display cocoa` opens
|
|
243
|
+
the window:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
bun ./index.js -fbr --arch arm64 --linux-lts -- \
|
|
247
|
+
-device ramfb -device virtio-keyboard-pci -device virtio-mouse-pci -display cocoa
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Once that image is built, boot it again without rebuilding (and without
|
|
251
|
+
Docker). `--linux-lts` belongs to the build, so leave it off `-r`:
|
|
252
|
+
|
|
253
|
+
```sh
|
|
254
|
+
bun ./index.js -r --arch arm64 -- \
|
|
255
|
+
-device ramfb -device virtio-keyboard-pci -device virtio-mouse-pci -display cocoa
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`start()` in the serial REPL, then `bunterm 2` opens bunterm on VT 2 in the
|
|
259
|
+
QEMU window and returns to the serial shell; type into the window. The
|
|
260
|
+
serial console is not a VT, so a bare `bunterm` there takes the active VT 1
|
|
261
|
+
and holds the serial shell until it exits; naming another VT starts it
|
|
262
|
+
detached (see [Multiple virtual consoles with bunterm](#multiple-virtual-consoles-with-bunterm)). The
|
|
263
|
+
firmware sets up ramfb at 800×600, and `init.js` loads `simpledrm` so it
|
|
264
|
+
becomes `/dev/fb0` (see [graphics.md](graphics.md#trying-it)).
|
|
237
265
|
* `--export` writes `buninu-linux-<version>-aarch64.img`.
|
|
238
266
|
* Before the kernel starts, Homebrew's aarch64 EDK2 prints a few
|
|
239
267
|
`Error: Image at … start failed` lines (its own drivers for hardware `virt`
|
|
@@ -283,6 +311,7 @@ Load only the hardware subsystem you need:
|
|
|
283
311
|
cfg.net
|
|
284
312
|
cfg.disk
|
|
285
313
|
cfg.power
|
|
314
|
+
cfg.sound
|
|
286
315
|
```
|
|
287
316
|
|
|
288
317
|
`cfg.net` loads the packaged wired-network and Android USB-tethering drivers and prints detected
|
|
@@ -298,7 +327,10 @@ ip route add default via 192.168.1.1
|
|
|
298
327
|
The real image initializes `/etc/resolv.conf` with `1.1.1.1` and `8.8.8.8`;
|
|
299
328
|
replace them if your network requires different DNS servers. `cfg.disk` loads
|
|
300
329
|
common SATA/PATA/SCSI, NVMe/VMD and USB-storage drivers so detected disks and
|
|
301
|
-
partitions appear in `/dev`.
|
|
330
|
+
partitions appear in `/dev`. `cfg.sound` unconditionally loads every sound
|
|
331
|
+
module packaged in the image, then lists the resulting `/dev/snd` devices; it
|
|
332
|
+
does not probe the hardware before loading modules. To inspect a filesystem
|
|
333
|
+
without mounting it:
|
|
302
334
|
|
|
303
335
|
```sh
|
|
304
336
|
mount -fv /dev/sda1 /mnt
|
|
@@ -475,7 +507,32 @@ below a shown image, and images scroll with the text. When the program you
|
|
|
475
507
|
started exits, the console returns to text mode. `bunterm --help` has the
|
|
476
508
|
options; [graphics.md](graphics.md) describes how it is built. The image
|
|
477
509
|
needs a kernel with a framebuffer: build with `--linux-lts` or `--real`
|
|
478
|
-
(the default `linux-virt` builds one only as modules the image omits).
|
|
510
|
+
(the default `linux-virt` builds one only as modules the image omits). The
|
|
511
|
+
aarch64 guest also needs a QEMU display; see
|
|
512
|
+
[What differs on aarch64](#what-differs-on-aarch64).
|
|
513
|
+
|
|
514
|
+
### Clipboard
|
|
515
|
+
|
|
516
|
+
`bunterm` supports copy and paste through OSC 52, so programs such as
|
|
517
|
+
`casty` and `jsmdcui` can use the clipboard. bunterm keeps nothing itself; it
|
|
518
|
+
passes every copy and paste to Buninu's `xclip -selection clipboard`. For
|
|
519
|
+
now that clipboard is a file, `$HOME/.xclip.clipboard`, read and written in
|
|
520
|
+
the ramdisk, so all bunterm sessions share it and it is gone after a reboot.
|
|
521
|
+
`bunterm --no-clipboard` turns OSC 52 off.
|
|
522
|
+
|
|
523
|
+
For Chinese input, run the pinyin input method
|
|
524
|
+
[jspinyin](https://www.npmjs.com/package/@drxiaozhi/jspinyin) in a bunterm
|
|
525
|
+
that is not inside a chroot, so it uses Buninu's `xclip`:
|
|
526
|
+
|
|
527
|
+
```sh
|
|
528
|
+
bun x @drxiaozhi/jspinyin
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
- jspinyin → casty: press `Ctrl-C` in jspinyin to copy its result box, then
|
|
532
|
+
`Ctrl-V` in casty pastes it through OSC 52.
|
|
533
|
+
- casty → jsmdcui / jspinyin: `Alt-C` in casty copies the selection through
|
|
534
|
+
OSC 52, and `Ctrl-V` pastes it in jsmdcui or jspinyin, since jsmdcui reads
|
|
535
|
+
from `xclip` first by default.
|
|
479
536
|
|
|
480
537
|
## Commands inside /bin
|
|
481
538
|
|
|
@@ -561,7 +618,10 @@ git clone https://github.com/jjtseng93/casty
|
|
|
561
618
|
cd casty/bin
|
|
562
619
|
# When clicking around, don’t release the mouse button immediately after pressing it, to make sure the mouse-down event is triggered
|
|
563
620
|
bun casty.js buninu.org
|
|
564
|
-
|
|
621
|
+
# Playing YouTube with sound(experimental)
|
|
622
|
+
# make sure you have run `cfg.sound` in bun-repl
|
|
623
|
+
# follow the dumped instructions
|
|
624
|
+
bun x bunproot --setup.pulse
|
|
565
625
|
|
|
566
626
|
|
|
567
627
|
# Download and enter an x64 Alpine minirootfs
|
|
@@ -753,7 +813,7 @@ its own.
|
|
|
753
813
|
```text
|
|
754
814
|
index.js entry point: -f / -b / -r / --arch / --docker / --export / --linux-lts / --real
|
|
755
815
|
Dockerfile the Debian 13 build toolchain for --docker (and macOS)
|
|
756
|
-
fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input modules
|
|
816
|
+
fetch-alpine.sh [fetch] kernel, EFI stub, musl, network/input/display modules
|
|
757
817
|
fetch-bun.sh [fetch] Bun, libstdc++, libgcc
|
|
758
818
|
build-uki.sh [build] UKI; calls scripts/pack-initramfs.sh
|
|
759
819
|
build-image.sh [build] GPT disk image; shared with the hello-world EFI
|
|
@@ -1089,7 +1149,7 @@ until `cfg.net` loads the packaged drivers and the user assigns an address and
|
|
|
1089
1149
|
route.
|
|
1090
1150
|
|
|
1091
1151
|
On a `--real` image the REPL also offers the getter-based `cfg` namespace:
|
|
1092
|
-
`cfg.net`, `cfg.disk`, `cfg.power` and `cfg.all` run as soon as the property is read, without
|
|
1152
|
+
`cfg.net`, `cfg.disk`, `cfg.power`, `cfg.sound` and `cfg.all` run as soon as the property is read, without
|
|
1093
1153
|
parentheses. `cfg.net` loads every packaged network module (including PHY and bus support),
|
|
1094
1154
|
then reads the `modalias` of every PCI and USB network device, matches it
|
|
1095
1155
|
against the `pci:`/`usb:` lines
|
|
@@ -1101,7 +1161,10 @@ declared dependencies. `--real` ships Intel `e1000`/`e1000e`/`igb`/`igc`,
|
|
|
1101
1161
|
Realtek `r8169`, Atheros `alx`, Broadcom `tg3`, common USB dongles, and
|
|
1102
1162
|
`rndis_host`/`cdc_ether`/`cdc_ncm`/`cdc_eem`/`cdc_subset`/`zaurus` for Android
|
|
1103
1163
|
USB tethering. `cfg.net` unconditionally attempts all of these packaged
|
|
1104
|
-
network modules. `cfg.
|
|
1164
|
+
network modules. `cfg.sound` likewise unconditionally attempts every packaged
|
|
1165
|
+
module below `kernel/sound/`, then lists `/dev/snd`; it does no hardware probe.
|
|
1166
|
+
The packaged sound set covers QEMU ES1371/HDA and common PC HDA/HDMI and USB
|
|
1167
|
+
audio where the selected kernel flavor provides them. `cfg.power` loads the ACPI
|
|
1105
1168
|
`battery`, `ac`, `button` and `thermal` modules through `/lib/modprobe.js`
|
|
1106
1169
|
and lists `/sys/class/power_supply` (a desktop without a battery simply
|
|
1107
1170
|
reports nothing registered). Nothing loads them at boot. PID 1 also installs
|
|
@@ -1136,6 +1199,7 @@ for the userspace session.
|
|
|
1136
1199
|
- [Pane and tab controls](#pane-and-tab-controls)
|
|
1137
1200
|
* [Using Bun Modern Shell](#using-bun-modern-shell)
|
|
1138
1201
|
* [Showing images](#showing-images)
|
|
1202
|
+
* [Clipboard](#clipboard)
|
|
1139
1203
|
- [Commands inside /bin](#commands-inside-bin)
|
|
1140
1204
|
- [Environment and dependencies](#environment-and-dependencies)
|
|
1141
1205
|
* [Build environment](#build-environment)
|
package/fetch-alpine.sh
CHANGED
|
@@ -81,6 +81,22 @@ if [ "$arch" = x86_64 ]; then
|
|
|
81
81
|
else
|
|
82
82
|
select_module virtio_input
|
|
83
83
|
fi
|
|
84
|
+
# The framebuffer for bunterm. Both lts kernels set CONFIG_SYSFB_SIMPLEFB, so
|
|
85
|
+
# the UEFI GOP (QEMU's ramfb on aarch64 virt) becomes a simple-framebuffer
|
|
86
|
+
# device that only simpledrm drives: x86_64 lts builds it in, aarch64 lts has
|
|
87
|
+
# it as a module. init.js loads it at boot, which is what makes /dev/fb0.
|
|
88
|
+
if [ "$linux_flavor" = lts ]; then
|
|
89
|
+
select_optional_module simpledrm
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Sound: a compact set covering QEMU's emulated AC97/HDA devices and the
|
|
93
|
+
# usual PC HDA/HDMI and USB-audio paths. Dependencies pull in the ALSA core,
|
|
94
|
+
# PCM, timer, codec and USB support needed by each available leaf driver.
|
|
95
|
+
# Some kernel flavors build fewer of these, so keep every leaf optional.
|
|
96
|
+
for module in snd-ens1371 snd-hda-intel snd-hda-codec-generic \
|
|
97
|
+
snd-hda-codec-hdmi snd-usb-audio; do
|
|
98
|
+
select_optional_module "$module"
|
|
99
|
+
done
|
|
84
100
|
|
|
85
101
|
if [ "${REAL_MACHINE:-}" = 1 ]; then
|
|
86
102
|
# USB keyboard for the console; common storage paths (SATA/AHCI HDD and
|
package/graphics.md
CHANGED
|
@@ -174,13 +174,22 @@ needs about 1 GB.
|
|
|
174
174
|
|
|
175
175
|
## Trying it
|
|
176
176
|
|
|
177
|
-
The framebuffer needs a kernel that provides one.
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
The framebuffer needs a kernel that provides one. Both `linux-lts` kernels
|
|
178
|
+
set `CONFIG_SYSFB_SIMPLEFB`, so the UEFI GOP becomes a `simple-framebuffer`
|
|
179
|
+
device, which only `simpledrm` drives (`efifb` never sees it). The x86_64
|
|
180
|
+
`linux-lts` builds `simpledrm` in. The aarch64 one has it as a module:
|
|
181
|
+
`fetch-alpine.sh` ships it and its DRM dependencies for lts builds, and
|
|
182
|
+
`init.js` loads it at boot. The default `linux-virt` builds the framebuffer
|
|
183
|
+
drivers as modules, and the image ships only the network, storage, USB and
|
|
184
|
+
HID modules, so there `/dev/fb0` never appears. Build with
|
|
185
|
+
`./index.js -b --linux-lts` (or `--real` on x86_64). Under QEMU, the x86_64
|
|
186
|
+
lts kernel gets a 1280×800 `simpledrm` framebuffer from the UEFI GOP even
|
|
187
|
+
with `-display none`, and the monitor's `screendump` shows it. QEMU's aarch64
|
|
188
|
+
`virt` machine has no display until you add `-device ramfb`; EDK2 then
|
|
189
|
+
provides an 800×600 GOP, and `virtio-keyboard-pci` gives the VTs a keyboard
|
|
190
|
+
(see the README's
|
|
191
|
+
[What differs on aarch64](README.md#what-differs-on-aarch64)). From a virtual
|
|
192
|
+
console in the guest:
|
|
184
193
|
|
|
185
194
|
```sh
|
|
186
195
|
bunterm # /bin/sh on this console
|
|
@@ -206,6 +215,13 @@ host, `jsmdcui --tui` on a Markdown file with a JPEG (bunterm sets
|
|
|
206
215
|
`JSMDCUI_KITTY_MODE=extended` for the program it runs) places the image
|
|
207
216
|
through its transmit / `a=p` / `a=d` sequence exactly as in the browser.
|
|
208
217
|
|
|
218
|
+
Verified on aarch64 (lts kernel, Hypervisor.framework, `-device ramfb
|
|
219
|
+
-device virtio-keyboard-pci -device virtio-mouse-pci`, `-display none`):
|
|
220
|
+
`/proc/fb` lists `simpledrmdrmfb`; `bunterm 2` from the serial shell returns
|
|
221
|
+
at once and draws on VT 2; and keys sent with the monitor's `sendkey` reach
|
|
222
|
+
it: `showimg /buninu/icon.png` places the kitty image, and the prompt's
|
|
223
|
+
colour emoji and the pointer render, all checked with `screendump`.
|
|
224
|
+
|
|
209
225
|
## Not done yet
|
|
210
226
|
|
|
211
227
|
- Selection and scrollback viewing in `bunterm` (the pointer itself works,
|
package/initramfs/bin/bunterm
CHANGED
|
@@ -11,7 +11,7 @@ const CANONICAL_BUNTERM = "/bin/bunterm";
|
|
|
11
11
|
|
|
12
12
|
const usage = () => {
|
|
13
13
|
console.error(`Usage:
|
|
14
|
-
bunterm [N | /dev/ttyN] [-s N | --font-size N] [--fb /dev/fb0] [--no-mouse] [-e command [argument...]]
|
|
14
|
+
bunterm [N | /dev/ttyN] [-s N | --font-size N] [--fb /dev/fb0] [--no-mouse] [--no-clipboard] [-e command [argument...]]
|
|
15
15
|
bunterm -h | --help`);
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -58,6 +58,8 @@ const main = async () => {
|
|
|
58
58
|
options.mouse = true;
|
|
59
59
|
} else if (argument === "--no-mouse") {
|
|
60
60
|
options.mouse = false;
|
|
61
|
+
} else if (argument === "--no-clipboard") {
|
|
62
|
+
options.clipboard = false;
|
|
61
63
|
} else if (/^[1-9][0-9]*$/.test(argument) || argument.startsWith("/dev/")) {
|
|
62
64
|
const number = /^[1-9][0-9]*$/.test(argument) ? Number(argument) : virtualConsoleNumber(argument);
|
|
63
65
|
if (number === null) throw new Error(`expected N or /dev/ttyN, got ${argument}`);
|
package/initramfs/init.js
CHANGED
|
@@ -179,6 +179,16 @@ try {
|
|
|
179
179
|
console.error(`init.js: input modules: ${error?.message ?? error}`);
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
// The framebuffer for bunterm: the firmware's GOP is a simple-framebuffer
|
|
183
|
+
// device, and simpledrm turns it into /dev/fb0. x86_64 linux-lts builds it
|
|
184
|
+
// in and linux-virt images do not ship it; tryModprobe skips both.
|
|
185
|
+
try {
|
|
186
|
+
const { tryModprobe } = await import("/lib/modprobe.js");
|
|
187
|
+
if (tryModprobe("simpledrm")?.length) console.log("init.js: loaded simpledrm");
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(`init.js: simpledrm: ${error?.message ?? error}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
182
192
|
const { default: repl } = await import("node:repl");
|
|
183
193
|
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
184
194
|
|
|
@@ -549,11 +559,26 @@ const configureDisks = () => {
|
|
|
549
559
|
return devices;
|
|
550
560
|
};
|
|
551
561
|
|
|
562
|
+
// Load the complete sound stack packaged in the image. Like cfg.net this is
|
|
563
|
+
// deliberately unconditional: it does not probe modaliases first, so codec
|
|
564
|
+
// and helper modules without their own discoverable device are loaded too.
|
|
565
|
+
const configureSound = () => {
|
|
566
|
+
const { modprobeAll } = require("/lib/modprobe.js");
|
|
567
|
+
const { readdirSync } = require("node:fs");
|
|
568
|
+
const report = modprobeAll({ accept: (_module, path) => path.startsWith("kernel/sound/") });
|
|
569
|
+
console.log(`sound: loaded all packaged sound modules (${report.inserted.length} newly loaded, ${report.errors.length} failed)`);
|
|
570
|
+
for (const { module, error } of report.errors) console.log(`sound: ${module}: ${error}`);
|
|
571
|
+
const devices = (() => { try { return readdirSync("/dev/snd"); } catch { return []; } })();
|
|
572
|
+
console.log(`sound: ${devices.length ? devices.map((name) => `/dev/snd/${name}`).join(" ") : "no sound devices registered"}`);
|
|
573
|
+
return devices;
|
|
574
|
+
};
|
|
575
|
+
|
|
552
576
|
globalThis.cfg = Object.freeze(Object.defineProperties({}, {
|
|
553
577
|
all: { enumerable: true, get: configureModules },
|
|
554
578
|
disk: { enumerable: true, get: configureDisks },
|
|
555
579
|
net: { enumerable: true, get: configureEthernet },
|
|
556
580
|
power: { enumerable: true, get: configureBattery },
|
|
581
|
+
sound: { enumerable: true, get: configureSound },
|
|
557
582
|
}));
|
|
558
583
|
|
|
559
584
|
// Compatibility aliases for images and notes that used the original API.
|
|
@@ -562,6 +587,7 @@ globalThis.cfgNet = configureEthernet;
|
|
|
562
587
|
globalThis.cfgDisk = configureDisks;
|
|
563
588
|
globalThis.cfgMod = configureModules;
|
|
564
589
|
globalThis.cfgBat = configureBattery;
|
|
590
|
+
globalThis.cfgSound = configureSound;
|
|
565
591
|
|
|
566
592
|
const showWelcome = () => {
|
|
567
593
|
console.log(`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// /lib/bunterm/clipboard.js — OSC 52 clipboard for bunterm, backed by xclip.
|
|
2
|
+
//
|
|
3
|
+
// program ── ESC ] 52 ; c ; <base64> BEL ──▶ xclip -selection clipboard
|
|
4
|
+
// program ── ESC ] 52 ; c ; ? BEL ─────────▶ xclip -selection clipboard -o
|
|
5
|
+
// ◀── ESC ] 52 ; c ; <base64> BEL ──
|
|
6
|
+
//
|
|
7
|
+
// bunterm keeps nothing itself: every copy and paste goes through xclip, so
|
|
8
|
+
// all bunterm sessions (and anything else using xclip) share one clipboard.
|
|
9
|
+
// Buninu's xclip prints status lines on stderr, which must not reach the
|
|
10
|
+
// console under the framebuffer, so stderr is discarded.
|
|
11
|
+
|
|
12
|
+
const runXclip = async (args, input) => {
|
|
13
|
+
const command = ["xclip", "-selection", "clipboard", ...args];
|
|
14
|
+
const proc = Bun.spawn(command, {
|
|
15
|
+
stdin: input === undefined ? "ignore" : input,
|
|
16
|
+
stdout: input === undefined ? "pipe" : "ignore",
|
|
17
|
+
stderr: "ignore",
|
|
18
|
+
});
|
|
19
|
+
const [output, exitCode] = await Promise.all([
|
|
20
|
+
input === undefined ? new Response(proc.stdout).arrayBuffer() : null,
|
|
21
|
+
proc.exited,
|
|
22
|
+
]);
|
|
23
|
+
if (exitCode !== 0) throw new Error(`${command.join(" ")} exited with ${exitCode}`);
|
|
24
|
+
return output;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Registers the OSC 52 handler on an xterm.js terminal. `reply` sends bytes
|
|
28
|
+
// back to the program, as the terminal's other replies do. A read the xclip
|
|
29
|
+
// call cannot answer is still answered, with empty contents, so the program
|
|
30
|
+
// does not wait out its own timeout.
|
|
31
|
+
export const attachClipboard = ({ term, reply, onError = () => {} }) =>
|
|
32
|
+
term.parser.registerOscHandler(52, (data) => {
|
|
33
|
+
const separator = data.indexOf(";");
|
|
34
|
+
if (separator < 0) return true;
|
|
35
|
+
const target = data.slice(0, separator);
|
|
36
|
+
const payload = data.slice(separator + 1);
|
|
37
|
+
|
|
38
|
+
if (payload === "?") {
|
|
39
|
+
// The reply is sent after xclip returns, when the program may have exited
|
|
40
|
+
// and the PTY closed; writing then throws, and an unhandled rejection
|
|
41
|
+
// would end bunterm before it restores the console.
|
|
42
|
+
runXclip(["-o"])
|
|
43
|
+
.catch((error) => { onError(error); return new ArrayBuffer(0); })
|
|
44
|
+
.then((bytes) => reply(`\u001b]52;${target};${Buffer.from(bytes).toString("base64")}\u0007`))
|
|
45
|
+
.catch(onError);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
runXclip([], Buffer.from(payload, "base64")).catch(onError);
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
@@ -21,6 +21,7 @@ import { Renderer, defaultTheme } from "./renderer.js";
|
|
|
21
21
|
import { ImageStore } from "./images.js";
|
|
22
22
|
import { openKeyboard, translateKeys } from "./input.js";
|
|
23
23
|
import { claimVirtualConsole } from "./vt.js";
|
|
24
|
+
import { attachClipboard } from "./clipboard.js";
|
|
24
25
|
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
26
27
|
|
|
@@ -52,6 +53,7 @@ export const createSession = ({
|
|
|
52
53
|
scrollback = 1000,
|
|
53
54
|
cursorBlink = true,
|
|
54
55
|
mouse = true,
|
|
56
|
+
clipboard = true,
|
|
55
57
|
env = {},
|
|
56
58
|
onError = (error) => console.error(`bunterm: ${error?.stack ?? error}`),
|
|
57
59
|
}) => {
|
|
@@ -158,6 +160,9 @@ export const createSession = ({
|
|
|
158
160
|
});
|
|
159
161
|
backend.onData(pump);
|
|
160
162
|
term.onData((data) => backend.write(Buffer.from(data, "utf8")));
|
|
163
|
+
// OSC 52 copy and paste go straight to xclip; replies return to the program.
|
|
164
|
+
// Without it (--no-clipboard) xterm.js ignores OSC 52 and nothing reaches xclip.
|
|
165
|
+
if (clipboard) attachClipboard({ term, reply: (text) => backend.write(Buffer.from(text, "utf8")), onError });
|
|
161
166
|
|
|
162
167
|
// Keyboard bytes from the console, rewritten for the program's key mode.
|
|
163
168
|
const input = (bytes) => {
|
|
@@ -12,6 +12,7 @@ bunterm /dev/tty1 # the full virtual-console spelling also works
|
|
|
12
12
|
bunterm -s 20 # short for --font-size 20
|
|
13
13
|
bunterm -e bun /buninu/apps/jsmdcui/src/index.js --demo
|
|
14
14
|
bunterm --no-mouse # keyboard only
|
|
15
|
+
bunterm --no-clipboard # ignore OSC 52 copy and paste
|
|
15
16
|
bunterm -h
|
|
16
17
|
```
|
|
17
18
|
|
|
@@ -39,6 +40,7 @@ identified from the process task name and its open console descriptor under
|
|
|
39
40
|
| `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
|
|
40
41
|
| `--no-blink` | A steady cursor. |
|
|
41
42
|
| `--no-mouse` | Do not attach a pointing device; nothing then reads `/dev/input`. |
|
|
43
|
+
| `--no-clipboard` | Ignore OSC 52: programs can neither copy to nor read from the clipboard, and `xclip` is never run. |
|
|
42
44
|
|
|
43
45
|
## What is drawn
|
|
44
46
|
|
|
@@ -101,6 +103,16 @@ mouse also needs the `usbhid` stack, which a `--real` image carries and
|
|
|
101
103
|
ones (a tablet or touchscreen) alike; a device that cannot be opened is
|
|
102
104
|
reported and the session continues without it.
|
|
103
105
|
|
|
106
|
+
## Clipboard
|
|
107
|
+
|
|
108
|
+
Programs copy and paste through OSC 52, as `casty` and `jsmdcui` do.
|
|
109
|
+
bunterm keeps no clipboard of its own: `ESC ] 52 ; c ; <base64>` is passed to
|
|
110
|
+
`xclip -selection clipboard`, and a query (`ESC ] 52 ; c ; ?`) is answered
|
|
111
|
+
with the output of `xclip -selection clipboard -o`, so every bunterm session
|
|
112
|
+
shares the same clipboard as `xclip`. A query xclip cannot answer is replied
|
|
113
|
+
to with empty contents. `--no-clipboard` turns this off, so a program can
|
|
114
|
+
neither set nor read the clipboard through the terminal.
|
|
115
|
+
|
|
104
116
|
## Limits
|
|
105
117
|
|
|
106
118
|
- Needs a framebuffer: `linux-lts` has one built in, while the default
|