diff --git a/README.md b/README.md index 8f64a4cf58..7269039f41 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Real, unmodified software compiled to WebAssembly: | Vim | 9.1 | Full editor with ncurses terminal UI | | NetHack | 3.6.7 | Classic roguelike with curses UI | | fbDOOM | (maximevince) | id Software's DOOM via the kernel's `/dev/fb0` Linux fbdev surface | +| espeak-ng | 1.52 | Speech synthesis; plays through upstream pcaudiolib's OSS backend on `/dev/dsp` | | Perl | 5.40 | Interpreter with core modules | | Ruby | 3.3 | Interpreter with core stdlib | | SpiderMonkey | 140 ESR | JavaScript engine backing the Node.js-compatible runtime with Intl, SharedArrayBuffer, worker_threads, and npm package installs. | @@ -321,6 +322,7 @@ bash packages/registry/nano/build-nano.sh # GNU nano 8.3 bash packages/registry/curl/build-curl.sh # curl bash packages/registry/netcat/build-netcat.sh # GNU Netcat 0.7.1 bash packages/registry/make/build-make.sh # GNU make +bash packages/registry/espeak-ng/build-espeak-ng.sh # espeak-ng 1.52 ``` See [docs/porting-guide.md](docs/porting-guide.md) for how to port your own software. diff --git a/abi/snapshot.json b/abi/snapshot.json index 5f15ade07d..493d334529 100644 --- a/abi/snapshot.json +++ b/abi/snapshot.json @@ -1224,6 +1224,12 @@ } }, "ioctl_request_contracts": { + "1074021776": { + "argKind": "scalar-i32", + "direction": "none", + "wasm32Size": 0, + "wasm64Size": 0 + }, "1074024452": { "argKind": "pointer", "direction": "in", @@ -1326,6 +1332,12 @@ "wasm32Size": 0, "wasm64Size": 0 }, + "2147763457": { + "argKind": "pointer", + "direction": "out", + "wasm32Size": 4, + "wasm64Size": 4 + }, "2147766274": { "argKind": "pointer", "direction": "out", @@ -1380,6 +1392,12 @@ "wasm32Size": 4, "wasm64Size": 4 }, + "2148025602": { + "argKind": "pointer", + "direction": "out", + "wasm32Size": 8, + "wasm64Size": 8 + }, "2148028435": { "argKind": "pointer", "direction": "out", @@ -1759,6 +1777,35 @@ "wasm64Size": null } }, + "ioctl_request_families": [ + { + "dir": 2, + "direction": "out", + "fixedSize": null, + "magic": 69, + "maxCallerSize": 256, + "nrFirst": 6, + "nrLast": 6 + }, + { + "dir": 2, + "direction": "out", + "fixedSize": null, + "magic": 69, + "maxCallerSize": 256, + "nrFirst": 32, + "nrLast": 63 + }, + { + "dir": 2, + "direction": "out", + "fixedSize": 24, + "magic": 69, + "maxCallerSize": null, + "nrFirst": 64, + "nrLast": 127 + } + ], "kernel_exports": [ { "kind": "func", @@ -2405,6 +2452,11 @@ "name": "kernel_inject_mouse_event", "signature": "(i32,i32,i32) -> ()" }, + { + "kind": "func", + "name": "kernel_input_event", + "signature": "(i32,i32,i32,i32) -> ()" + }, { "kind": "func", "name": "kernel_ioctl", @@ -2970,6 +3022,11 @@ "name": "kernel_set_fork_fd_action", "signature": "(i32,i32,i32) -> (i32)" }, + { + "kind": "func", + "name": "kernel_set_input_canvas_dims", + "signature": "(i32,i32) -> ()" + }, { "kind": "func", "name": "kernel_set_max_addr", diff --git a/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts b/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts index 7678ac0232..c24b68755e 100644 --- a/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts +++ b/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts @@ -7,6 +7,9 @@ import { bindImageOwnedRuntimeUrls, type ImageOwnedRuntimeLazyAssets, } from "../../../lib/init/image-owned-runtime-urls"; +import { BrowserInputSource } from "../../../../../host/src/input/browser-input-source"; +import { HttpBridgeHost } from "../../../lib/http-bridge"; +import { rewriteShellLazyFileUrls } from "../../../lib/init/shell-lazy-files"; import { resolveShellLazyArchiveUrl } from "../../../lib/init/lazy-archives"; import { WORDPRESS_CONFIG_INIT_SCRIPT, @@ -24,6 +27,10 @@ import { WORDPRESS_MARIADB_SOCKET_PATH, } from "../../../lib/init/wordpress-mariadb-readiness"; import { MemoryFileSystem } from "../../../../../host/src/vfs/memory-fs"; +import { + extractZipEntry, + parseZipCentralDirectory, +} from "../../../../../host/src/vfs/zip"; import { loadHomebrewBottleMirrorClosedAssets } from "../../../../../host/src/homebrew-bottle-mirror-browser"; import { HOMEBREW_BOTTLE_MIRROR_PLAN_VFS_PATH } from "../../../../../host/src/homebrew-bottle-mirror-plan"; import { @@ -195,6 +202,26 @@ const OPTIONAL_BINARY_URLS = { import: "default", }, ), + ...import.meta.glob("../../../../../local-binaries/programs/wasm32/evdev_demo.wasm", { + query: "?url", import: "default", + }), + ...import.meta.glob("../../../../../binaries/programs/wasm32/evdev_demo.wasm", { + query: "?url", import: "default", + }), + // espeak-ng publishes a wasm output plus a runtime file, so the resolver + // mirrors its whole closure under the package directory. + ...import.meta.glob("../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", { + query: "?url", import: "default", + }), + ...import.meta.glob("../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", { + query: "?url", import: "default", + }), + ...import.meta.glob("../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", { + query: "?url", import: "default", + }), + ...import.meta.glob("../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", { + query: "?url", import: "default", + }), } as Record Promise>; async function optionalBinaryUrl( @@ -205,7 +232,11 @@ async function optionalBinaryUrl( const loader = OPTIONAL_BINARY_URLS[relPath]; if (loader) return loader(); } - throw new Error(`${label} is not built. Run: ./run.sh build programs`); + throw new Error( + `${label} is not built. Run: ./run.sh build programs, ` + + `or for package-owned binaries: ` + + `cargo xtask build-deps resolve `, + ); } type GalleryPackageRequirement = { @@ -393,6 +424,8 @@ const LIVE_DEMO_IDS = [ "wordpress-mariadb", "doom", "modeset", + "evdev", + "espeak", ] as const; type LiveDemoId = (typeof LIVE_DEMO_IDS)[number]; @@ -482,6 +515,12 @@ const LIVE_DEMO_SPECS: Record = { image: "shell", features: ["kms"], }, + evdev: { + image: "shell", + }, + espeak: { + image: "shell", + }, }; const DEFAULT_DEMO_FOR_VFS_IMAGE: Record = { @@ -540,6 +579,22 @@ interface LiveProfile { }; }; framebufferTest: boolean; + /** + * Stage `evdev_demo` into `/usr/local/bin`, attach a `BrowserInputSource` + * to the window so keyboard/pointer events flow into the kernel's + * `/dev/input/event{0,1}`, and run the binary from bash so its event + * log streams to the user's Shell pane. The C1 sysroot vendoring proof. + */ + evdevDemo: boolean; + /** + * Spawn `espeak-ng "..."` from the booted shell. espeak-ng links + * upstream pcaudiolib built with only its OSS backend, so + * `create_audio_device_object` falls through to `/dev/dsp` and a + * single binary invocation produces audible synthesised speech + * without any host-side pipeline. The binary + data dir are baked + * into the image via `stageEspeakRuntime`. + */ + espeakDemo: boolean; } interface WebReadinessState { @@ -999,6 +1054,8 @@ function customVfsProfile( shell: "default", maxVfsByteLength: CUSTOM_VFS_PROFILE_MAX_BYTES, framebufferTest: fb === "test", + evdevDemo: false, + espeakDemo: false, }; } @@ -1018,6 +1075,8 @@ function profileFor(id: string, fb?: FbDemo): LiveProfile { fallbackPresentation: software.presentation, init: software.init, framebufferTest: false, + evdevDemo: false, + espeakDemo: false, }; } @@ -1065,6 +1124,8 @@ function profileFor(id: string, fb?: FbDemo): LiveProfile { }, }, framebufferTest: fb === "test", + evdevDemo: normalized === "evdev", + espeakDemo: normalized === "espeak", }; } @@ -1520,6 +1581,18 @@ async function bootProfile( ensureDirRecursive(buildFs, dirname(profile.init.argv[0])); writeVfsBinary(buildFs, profile.init.argv[0], new Uint8Array(bytes), 0o755); } + // Both demos run their binary from a path, so the bytes have to be in the + // image before the worker takes exclusive ownership of the VFS. + if (profile.espeakDemo) { + tick("staging espeak-ng..."); + await stageEspeakRuntime(buildFs); + assertCurrent(); + } + if (profile.evdevDemo) { + tick("staging evdev_demo..."); + await stageEvdevDemo(buildFs); + assertCurrent(); + } ensureDemoHomes(buildFs); } // Bake the shell + gallery-software binaries into the image before the @@ -1850,6 +1923,49 @@ async function bootProfile( tick, assertCurrent, ); + } else if (profile.espeakDemo) { + // The binary and its voice data are already in the image; see + // stageEspeakRuntime. Playback rides the /dev/dsp path every other + // sound demo uses. + void (async () => { + try { + tick("running espeak-ng..."); + await host.runShellCommand( + `/usr/bin/espeak-ng "Welcome to Kandelo, the WebAssembly POSIX kernel"`, + ); + tick("espeak-ng exited"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + tick(`espeak-ng failed: ${msg}`); + } + })(); + } else if (profile.evdevDemo) { + // autoCommand can't run this: the InputSource must be attached before + // the binary starts polling /dev/input/event{0,1}. The binary itself is + // already in the image; see stageEvdevDemo. + const kernelForEvdev = kernel; + void (async () => { + try { + tick("attaching input source..."); + kernelForEvdev.attachInputSource(new BrowserInputSource(window), { + width: window.innerWidth, + height: window.innerHeight, + }); + tick("running evdev_demo..."); + // evdev_demo runs forever; runShellCommand resolves when the + // bash prompt reappears (it never will) or rejects after its + // internal 5-minute timeout. Both are expected — log neutrally. + await host.runShellCommand("/usr/local/bin/evdev_demo"); + tick("evdev_demo exited"); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + if (/timed out waiting for PTY prompt/.test(msg)) { + tick("evdev_demo running (long-tail; no further status updates)"); + } else { + tick(`evdev_demo failed: ${msg}`); + } + } + })(); } else if (presentation?.autoCommand) { tick("starting configured command from the default shell..."); void host.runShellCommand(presentation.autoCommand).catch((err) => { @@ -1924,6 +2040,54 @@ function stageShellUtilities( } } +/** + * Bake espeak-ng and its voice data into the image. + * + * Both come from the espeak-ng package closure, so the demo consumes the same + * bytes the resolver published. libespeak-ng's PATH_ESPEAK_DATA is fixed to + * /usr/share at build time, so the data tree has to land unpacked there. + */ +async function stageEspeakRuntime(fs: MemoryFileSystem): Promise { + const binaryUrl = await optionalBinaryUrl([ + "../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", + "../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", + ], "espeak-ng.wasm"); + const binary = await fetch(binaryUrl) + .then(failOn("espeak-ng.wasm")) + .then((r) => r.arrayBuffer()); + ensureDirRecursive(fs, "/usr/bin"); + writeVfsBinary(fs, "/usr/bin/espeak-ng", new Uint8Array(binary), 0o755); + + const dataUrl = await optionalBinaryUrl([ + "../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", + "../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", + ], "espeak-ng-data.zip"); + const data = await fetch(dataUrl) + .then(failOn("espeak-ng-data.zip")) + .then((r) => r.arrayBuffer()); + const zipBytes = new Uint8Array(data); + const root = "/usr/share/espeak-ng-data"; + ensureDirRecursive(fs, root); + for (const entry of parseZipCentralDirectory(zipBytes)) { + if (entry.isDirectory) continue; + const target = `${root}/${entry.fileName}`; + ensureDirRecursive(fs, target.slice(0, target.lastIndexOf("/"))); + writeVfsBinary(fs, target, extractZipEntry(zipBytes, entry), 0o644); + } +} + +async function stageEvdevDemo(fs: MemoryFileSystem): Promise { + const url = await optionalBinaryUrl([ + "../../../../../local-binaries/programs/wasm32/evdev_demo.wasm", + "../../../../../binaries/programs/wasm32/evdev_demo.wasm", + ], "evdev_demo.wasm"); + const bytes = await fetch(url) + .then(failOn("evdev_demo.wasm")) + .then((r) => r.arrayBuffer()); + ensureDirRecursive(fs, "/usr/local/bin"); + writeVfsBinary(fs, "/usr/local/bin/evdev_demo", new Uint8Array(bytes), 0o755); +} + function ensureDemoHomes(fs: MemoryFileSystem): void { ensureDirRecursive(fs, "/home"); ensureOwnedDir(fs, DEMO_HOME, 0o755, DEMO_UID, DEMO_GID); diff --git a/apps/browser-demos/pages/kandelo/presets.ts b/apps/browser-demos/pages/kandelo/presets.ts index 776c142df8..9334246892 100644 --- a/apps/browser-demos/pages/kandelo/presets.ts +++ b/apps/browser-demos/pages/kandelo/presets.ts @@ -128,4 +128,26 @@ export const PRESET_LIBRARY: Preset[] = [ bootCommand: ["/usr/local/bin/modeset"], estimatedUrlBytes: 612, }, + { + id: "evdev", + title: "Evdev input log", + summary: "Keystrokes + pointer motion captured from the DOM and replayed through /dev/input/event{0,1}.", + base: SHELL_BASE, + packages: ["bash@local", "coreutils@local"], + accent: "#7e57c2", + glyph: "E", + bootCommand: ["bash", "-l", "-i"], + estimatedUrlBytes: 612, + }, + { + id: "espeak", + title: "OSS - Espeak-NG", + summary: "The kernel speaks: espeak-ng synthesises text directly through libpcaudio's OSS backend.", + base: SHELL_BASE, + packages: ["bash@local", "coreutils@local"], + accent: "#f48fb1", + glyph: "T", + bootCommand: ["bash", "-l", "-i"], + estimatedUrlBytes: 612, + }, ]; diff --git a/apps/browser-demos/test/kandelo-espeak.spec.ts b/apps/browser-demos/test/kandelo-espeak.spec.ts new file mode 100644 index 0000000000..322928f659 --- /dev/null +++ b/apps/browser-demos/test/kandelo-espeak.spec.ts @@ -0,0 +1,47 @@ +import { expect, test, type Page } from "@playwright/test"; + +const appUrl = (path: string): string => { + const baseUrl = process.env.KANDELO_TEST_BASE_URL; + return baseUrl ? new URL(path, baseUrl).href : path; +}; + +async function gotoOrSkip(page: Page, path: string) { + await page.goto(appUrl(path), { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(2_000); + if (await page.locator("vite-error-overlay").count()) { + test.skip(true, "Required binary not built - Vite import error"); + } +} + +async function terminalText(page: Page): Promise { + return page.locator(".xterm-rows").first().evaluate((node) => node.textContent ?? ""); +} + +test("Kandelo espeak-ng demo speaks through pcaudiolib + /dev/dsp", async ({ page }) => { + test.setTimeout(300_000); + + await gotoOrSkip(page, "/?demo=espeak"); + + // Web Audio starts only after a trusted gesture. App.tsx activates the + // PCM sink from a capturing pointerdown listener, so a physical click + // anywhere moves the machine to its running audio state. + await page.locator("body").click({ position: { x: 5, y: 5 } }); + await expect(page.locator("[data-audio-state]")).toHaveAttribute( + "data-audio-state", + "running", + { timeout: 60_000 }, + ); + + // The boot-path branch in live-setup.ts runs + // `espeak-ng "Welcome to Kandelo, the WebAssembly POSIX kernel"`. + // espeak-ng prints a few status lines on stderr; the more reliable + // signal that the synth path worked end-to-end is the bash prompt + // reappearing after the binary exits. We watch for the trailing + // shell prompt instead of a specific espeak output line so the test + // doesn't break on cosmetic CLI changes upstream. pcaudiolib aborts + // the run when it cannot open /dev/dsp, so reaching the prompt with + // a running sink proves the OSS backend negotiated the device. + await expect + .poll(() => terminalText(page), { timeout: 180_000 }) + .toMatch(/[#$]\s*$/); +}); diff --git a/apps/browser-demos/test/kandelo-evdev.spec.ts b/apps/browser-demos/test/kandelo-evdev.spec.ts new file mode 100644 index 0000000000..8d404bace8 --- /dev/null +++ b/apps/browser-demos/test/kandelo-evdev.spec.ts @@ -0,0 +1,58 @@ +import { expect, test, type Page } from "@playwright/test"; + +const appUrl = (path: string): string => { + const baseUrl = process.env.KANDELO_TEST_BASE_URL; + return baseUrl ? new URL(path, baseUrl).href : path; +}; + +async function gotoOrSkip(page: Page, path: string) { + await page.goto(appUrl(path), { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(2_000); + if (await page.locator("vite-error-overlay").count()) { + test.skip(true, "Required binary not built - Vite import error"); + } +} + +async function terminalText(page: Page): Promise { + return page.locator(".xterm-rows").first().evaluate((node) => node.textContent ?? ""); +} + +test("Kandelo evdev demo forwards keystrokes + pointer through /dev/input/event{0,1}", async ({ page }) => { + test.setTimeout(300_000); + + await gotoOrSkip(page, "/?demo=evdev"); + + // The evdev_demo binary prints "ready:" once both /dev/input/event0 + // and /dev/input/event1 have been opened and EVIOCGNAME has succeeded + // on both. Waiting for that proves: the binary was staged into the + // VFS, bash exec'd it, and the kernel's A3 EVIOC* dispatch returned + // the correct device names. + await expect + .poll(() => terminalText(page), { timeout: 180_000 }) + .toContain("ready:"); + + const readyText = await terminalText(page); + expect(readyText).toContain("kbd: wpk virtual keyboard"); + expect(readyText).toContain("ptr: wpk virtual pointer"); + + // BrowserInputSource preventDefaults every key it translates, so when + // the terminal pane is focused the only way "key down: code=30" + // (KEY_A) can appear in the xterm output is if BrowserInputSource + // caught the keydown, dispatched into kernel_input_event, the kernel + // fanned out to /dev/input/event0, and evdev_demo's read returned it. + // The dual-host parity claim of B4 is what this proves end-to-end. + await page.keyboard.press("KeyA"); + await expect + .poll(() => terminalText(page), { timeout: 15_000 }) + .toMatch(/key down: code=30/); + + // Pointer move → ABS_X/ABS_Y (pointer-lock inactive) → evdev_demo + // prints "ptr abs code=0 value=N" (REL_X==ABS_X==0 in Linux UAPI). + // The exact value depends on which DOM element pointermove fires on + // and its offsetX/offsetY, so just assert the shape of the line. + await page.mouse.move(100, 200); + await page.mouse.move(150, 250); + await expect + .poll(() => terminalText(page), { timeout: 15_000 }) + .toMatch(/ptr (abs|rel) code=\d+ value=-?\d+/); +}); diff --git a/crates/kernel/src/devfs.rs b/crates/kernel/src/devfs.rs index f6b862331c..616efa07c2 100644 --- a/crates/kernel/src/devfs.rs +++ b/crates/kernel/src/devfs.rs @@ -166,8 +166,11 @@ fn dir_entries(proc: &crate::process::Process, entry: &DevfsEntry) -> Vec<(Vec { // /dev/input/mice — Linux-compatible PS/2 mouse stream. - // No /dev/input/eventN evdev nodes yet (mousedev surface only). entries.push((b"mice".into(), DT_CHR, devfs_ino(b"/dev/input/mice"))); + // /dev/input/event0 — keyboard evdev (plan 5). + // /dev/input/event1 — pointer evdev. + entries.push((b"event0".into(), DT_CHR, devfs_ino(b"/dev/input/event0"))); + entries.push((b"event1".into(), DT_CHR, devfs_ino(b"/dev/input/event1"))); } DevfsEntry::DriDir => { // /dev/dri/card0 — KMS / display side. @@ -523,4 +526,20 @@ mod tests { Err(Errno::EINVAL) ); } + + #[test] + fn event0_and_event1_listed_in_dev_input_dir() { + let proc = crate::process::Process::new(1); + let entries = dir_entries(&proc, &DevfsEntry::InputDir); + let names: Vec<&[u8]> = entries.iter().map(|(n, _, _)| n.as_slice()).collect(); + assert!(names.iter().any(|n| *n == b"event0"), "event0 missing: {:?}", names); + assert!(names.iter().any(|n| *n == b"event1"), "event1 missing: {:?}", names); + // event2 deliberately NOT synthesised. + assert!(!names.iter().any(|n| *n == b"event2")); + for (name, dtype, _) in entries.iter() { + if name.as_slice() == b"event0" || name.as_slice() == b"event1" { + assert_eq!(*dtype, DT_CHR); + } + } + } } diff --git a/crates/kernel/src/fork.rs b/crates/kernel/src/fork.rs index 46dee82332..2f4d92a8f2 100644 --- a/crates/kernel/src/fork.rs +++ b/crates/kernel/src/fork.rs @@ -639,6 +639,12 @@ const DRI_TAG_RENDER_NODE: u8 = 1; const DRI_TAG_CARD: u8 = 2; const DRI_TAG_PRIME_BO: u8 = 3; +const INPUT_TAG_NONE: u8 = 0; +const INPUT_TAG_SOME: u8 = 1; + +const PCM_DIR_PLAYBACK: u8 = 0; +const PCM_DIR_CAPTURE: u8 = 1; + fn write_dri_fd_state(w: &mut Writer<'_>, dri: &crate::ofd::DriFdState) -> Result<(), Errno> { w.write_u32(dri.handles.len() as u32)?; for (handle, bo_id) in &dri.handles { @@ -706,6 +712,28 @@ fn write_dri_state( } } +/// Serialise the evdev sidecar across a fork/exec. The ring is copied +/// byte-for-byte; the child inherits the parent's grab + dropped flag. +/// `EVIOCGRAB` is per-OFD in Linux, so each side of the fork ends up +/// with its own copy of the InputFdState. +fn write_input_state( + w: &mut Writer<'_>, + state: Option<&crate::ofd::InputFdState>, +) -> Result<(), Errno> { + let Some(input) = state else { + return w.write_u8(INPUT_TAG_NONE); + }; + w.write_u8(INPUT_TAG_SOME)?; + w.write_u8(input.device)?; + w.write_u8(input.grabbed as u8)?; + w.write_u8(input.dropped as u8)?; + w.write_u32(input.event_ring.len() as u32)?; + for &b in input.event_ring.iter() { + w.write_u8(b)?; + } + Ok(()) +} + /// Read a `DriFdState` from the wire and incref every referenced bo /// in the global registry so the new OFD has its own refcount. The /// caller may still drop the entire OFD if the surrounding deserialize @@ -788,6 +816,43 @@ fn read_kms_fd_state(r: &mut Reader<'_>) -> Result, +) -> Result>, Errno> { + use alloc::collections::VecDeque; + let tag = r.read_u8()?; + match tag { + INPUT_TAG_NONE => Ok(None), + INPUT_TAG_SOME => { + let device = r.read_u8()?; + let grabbed = r.read_u8()? != 0; + let dropped = r.read_u8()? != 0; + let ring_len = r.read_u32()? as usize; + // The ring is always whole 24-byte records and bounded at + // INPUT_RING_MAX_BYTES — reject anything else as a + // corrupted/forged fork stream. + if ring_len > crate::ofd::INPUT_RING_MAX_BYTES + || ring_len % core::mem::size_of::< + wasm_posix_shared::input::WpkInputEvent, + >() != 0 + { + return Err(Errno::EINVAL); + } + let mut event_ring = VecDeque::with_capacity(ring_len); + for _ in 0..ring_len { + event_ring.push_back(r.read_u8()?); + } + Ok(Some(alloc::boxed::Box::new(crate::ofd::InputFdState { + device, + event_ring, + grabbed, + dropped, + }))) + } + _ => Err(Errno::EINVAL), + } +} + fn read_dri_state( r: &mut Reader<'_>, ) -> Result>, Errno> { @@ -928,6 +993,11 @@ pub fn serialize_fork_state(proc: &Process, buf: &mut [u8]) -> Result Result<(), Er ofd_entries.push(None); } let dri_state = read_dri_state(&mut r)?; + let input_state = read_input_state(&mut r)?; let mut ofd = OpenFileDesc { ofd_id, file_id, @@ -1277,6 +1348,7 @@ fn deserialize_fork_state_into(buf: &[u8], child: &mut Process) -> Result<(), Er dir_position_generation: 0, dir_pending_entry: None, dri_state, + input_state, }; ofd.reset_directory_iterator_for_reopen(); ofd_entries[index] = Some(ofd); @@ -1758,6 +1830,11 @@ pub fn serialize_exec_state(proc: &Process, buf: &mut [u8]) -> Result Result { ofd_entries.push(None); } let dri_state = read_dri_state(&mut r)?; + let input_state = read_input_state(&mut r)?; let mut ofd = OpenFileDesc { ofd_id, file_id, @@ -1952,6 +2030,7 @@ pub fn deserialize_exec_state(buf: &[u8], pid: u32) -> Result { dir_position_generation: 0, dir_pending_entry: None, dri_state, + input_state, }; ofd.reset_directory_iterator_for_reopen(); ofd_entries[index] = Some(ofd); @@ -3496,4 +3575,5 @@ mod tests { "exec keeps the same process identity; KMS master should survive" ); } + } diff --git a/crates/kernel/src/input/dispatch.rs b/crates/kernel/src/input/dispatch.rs new file mode 100644 index 0000000000..2e66512e3a --- /dev/null +++ b/crates/kernel/src/input/dispatch.rs @@ -0,0 +1,191 @@ +//! Event producer for `/dev/input/event{0,1}`. Mirrors Linux +//! `drivers/input/evdev.c::evdev_pass_values`: a full ring discards +//! the incoming record and latches `dropped`; the next read prepends +//! a synthetic `SYN_DROPPED` so userspace can resync via `EVIOCG*`. + +use alloc::collections::VecDeque; + +use wasm_posix_shared::input::WpkInputEvent; + +use crate::ofd::INPUT_RING_MAX_BYTES; + +const RECORD_SIZE: usize = core::mem::size_of::(); + +/// Push one `WpkInputEvent` onto every open OFD bound to `device` +/// (0 = keyboard, 1 = pointer). Other device numbers are dropped. +/// +/// Returns the count of OFDs that accepted the record (drops do not +/// count). `tv_sec` / `tv_usec` are supplied by the caller so this +/// function stays testable without a host. +pub fn push_event( + device: u8, + ev_type: u16, + code: u16, + value: i32, + tv_sec: i64, + tv_usec: i32, +) -> usize { + if device > 1 { + return 0; + } + let ev = WpkInputEvent { + tv_sec, + tv_usec, + _pad: 0, + ev_type, + code, + value, + }; + let mut delivered = 0; + crate::process_table::with_processes(|procs| { + for proc in procs { + for (_idx, ofd) in proc.ofd_table.iter_mut() { + let Some(input) = ofd.input_mut() else { continue }; + if input.device != device { + continue; + } + if input.event_ring.len() + RECORD_SIZE > INPUT_RING_MAX_BYTES { + input.dropped = true; + continue; + } + push_record(&mut input.event_ring, &ev); + delivered += 1; + } + } + }); + delivered +} + +fn push_record(ring: &mut VecDeque, ev: &WpkInputEvent) { + let bytes: [u8; RECORD_SIZE] = unsafe { + core::mem::transmute::(*ev) + }; + for &b in bytes.iter() { + ring.push_back(b); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ofd::{FileType, InputFdState, INPUT_RING_MAX_RECORDS}; + use crate::process::Process; + use crate::process_table::GLOBAL_PROCESS_TABLE as PROCESS_TABLE; + use alloc::boxed::Box; + use wasm_posix_shared::flags::O_RDWR; + use wasm_posix_shared::input::{EV_KEY, EV_REL, EV_SYN, KEY_A, REL_X, SYN_REPORT}; + + // Tests mutate the global PROCESS_TABLE and only assert on their + // own pids, so concurrent runs are independent. + fn install_process(pid: u32) -> &'static mut Process { + let table = unsafe { &mut *PROCESS_TABLE.0.get() }; + table.processes.insert(pid, Process::new(pid)); + let proc = table.processes.get_mut(&pid).unwrap(); + unsafe { &mut *(proc as *mut Process) } + } + + fn install_input_ofd(proc: &mut Process, device: u8) -> usize { + let host_handle = if device == 0 { -10 } else { -11 }; + let path: alloc::vec::Vec = if device == 0 { + b"/dev/input/event0".to_vec() + } else { + b"/dev/input/event1".to_vec() + }; + let ofd_idx = proc + .ofd_table + .create(FileType::CharDevice, O_RDWR, host_handle, path); + let ofd = proc.ofd_table.get_mut(ofd_idx).unwrap(); + ofd.input_state = Some(Box::new(InputFdState { + device, + ..Default::default() + })); + ofd_idx + } + + fn ring_records(proc: &Process, ofd_idx: usize) -> usize { + proc.ofd_table.get(ofd_idx).unwrap().input().unwrap().event_ring.len() + / RECORD_SIZE + } + + #[test] + fn push_event_with_unknown_device_is_a_noop() { + let _ = install_process(7001); + let delivered = push_event(2, EV_KEY, KEY_A, 1, 0, 0); + assert_eq!(delivered, 0); + } + + #[test] + fn push_event_writes_24_byte_record_to_matching_ofd() { + let proc = install_process(7002); + let ofd_idx = install_input_ofd(proc, 0); + assert_eq!(ring_records(proc, ofd_idx), 0); + + push_event(0, EV_KEY, KEY_A, 1, 42, 1000); + + assert_eq!(ring_records(proc, ofd_idx), 1); + let input = proc.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + let tv_sec_bytes: [u8; 8] = input + .event_ring + .iter() + .take(8) + .copied() + .collect::>() + .try_into() + .unwrap(); + assert_eq!(i64::from_le_bytes(tv_sec_bytes), 42); + } + + #[test] + fn push_event_skips_other_device() { + let proc = install_process(7003); + let kbd = install_input_ofd(proc, 0); + let ptr = install_input_ofd(proc, 1); + + push_event(1, EV_REL, REL_X, 5, 0, 0); + + assert_eq!(ring_records(proc, kbd), 0); + assert_eq!(ring_records(proc, ptr), 1); + } + + #[test] + fn ring_overflow_sets_dropped_and_discards_new_records() { + let proc = install_process(7004); + let ofd_idx = install_input_ofd(proc, 0); + for i in 0..INPUT_RING_MAX_RECORDS { + push_event(0, EV_KEY, KEY_A, i as i32, 0, 0); + } + assert_eq!(ring_records(proc, ofd_idx), INPUT_RING_MAX_RECORDS); + assert!(!proc.ofd_table.get(ofd_idx).unwrap().input().unwrap().dropped); + + // Linux semantics: ring stays at max, `dropped` latches on, + // the incoming record is the one discarded. + push_event(0, EV_KEY, KEY_A, 0xdead, 0, 0); + assert_eq!(ring_records(proc, ofd_idx), INPUT_RING_MAX_RECORDS); + assert!( + proc.ofd_table.get(ofd_idx).unwrap().input().unwrap().dropped, + "dropped flag must latch on overflow" + ); + + push_event(0, EV_KEY, KEY_A, 0xbeef, 0, 0); + assert_eq!(ring_records(proc, ofd_idx), INPUT_RING_MAX_RECORDS); + } + + #[test] + fn push_event_fans_out_to_every_open_ofd_for_the_device() { + let proc = install_process(7006); + let a = install_input_ofd(proc, 0); + let b = install_input_ofd(proc, 0); + push_event(0, EV_KEY, KEY_A, 1, 0, 0); + assert_eq!(ring_records(proc, a), 1); + assert_eq!(ring_records(proc, b), 1); + } + + #[test] + fn push_event_syn_report_lands_in_ring_verbatim() { + let proc = install_process(7007); + let ofd_idx = install_input_ofd(proc, 0); + push_event(0, EV_KEY, KEY_A, 1, 0, 0); + push_event(0, EV_SYN, SYN_REPORT, 0, 0, 0); + assert_eq!(ring_records(proc, ofd_idx), 2); + } +} diff --git a/crates/kernel/src/input/mod.rs b/crates/kernel/src/input/mod.rs new file mode 100644 index 0000000000..747c63ef79 --- /dev/null +++ b/crates/kernel/src/input/mod.rs @@ -0,0 +1,168 @@ +//! evdev input subsystem — backs `/dev/input/event{0,1}`. +//! +//! Covers the canvas-dim cache used to size `EVIOCGABS(ABS_X/ABS_Y)`, +//! the `EVIOCGBIT(*)` bitmap helper, and (in [`dispatch`]) the host- +//! callable event fan-out. + +pub mod dispatch; + +use core::sync::atomic::{AtomicU32, Ordering}; + +use wasm_posix_shared::input::*; + +/// Canvas pixel dimensions used by `EVIOCGABS(ABS_X/ABS_Y)` on the +/// pointer device. The host sets these once a KMS canvas attaches +/// (A4 wires `HostIO`'s canvas-dims push); until then the default +/// is 1280×720 so SDL2 probes don't see a degenerate 0-wide axis +/// and reject the device. +static CANVAS_W: AtomicU32 = AtomicU32::new(1280); +static CANVAS_H: AtomicU32 = AtomicU32::new(720); + +pub fn canvas_dims() -> (u32, u32) { + (CANVAS_W.load(Ordering::Relaxed), CANVAS_H.load(Ordering::Relaxed)) +} + +/// Update the canvas-dim cache. Both dimensions are clamped to at +/// least 1 so `maximum = w - 1` in the EVIOCGABS reply doesn't go +/// negative. +pub fn set_canvas_dims(width: u32, height: u32) { + CANVAS_W.store(width.max(1), Ordering::Relaxed); + CANVAS_H.store(height.max(1), Ordering::Relaxed); +} + +fn set_bit(buf: &mut [u8], bit: u16) { + let byte = (bit as usize) >> 3; + let shift = (bit as usize) & 7; + if byte < buf.len() { + buf[byte] |= 1 << shift; + } +} + +/// Populate `buf` (already zeroed) with the bitmap returned by +/// `EVIOCGBIT(ev_type, len)` for the given device (`0` = keyboard, +/// `1` = pointer). Out-of-range bits are silently dropped — Linux +/// truncates to whatever buffer length the caller passed. +pub fn populate_evbit(device: u8, ev_type: u16, buf: &mut [u8]) { + match (device, ev_type) { + (_, 0) => { + set_bit(buf, EV_SYN); + set_bit(buf, EV_KEY); + if device == 1 { + set_bit(buf, EV_REL); + set_bit(buf, EV_ABS); + } + } + // A1 picked 1..=KEY_MICMUTE precisely so this is a single + // loop instead of a 248-entry table. KEY_RESERVED (0) is + // skipped so the bitmap matches Linux byte-for-byte. + (0, t) if t == EV_KEY => { + for k in 1..=KEY_MICMUTE { + set_bit(buf, k); + } + } + (1, t) if t == EV_KEY => { + for &b in &[BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_SIDE, BTN_EXTRA] { + set_bit(buf, b); + } + } + (1, t) if t == EV_REL => { + set_bit(buf, REL_X); + set_bit(buf, REL_Y); + set_bit(buf, REL_WHEEL); + set_bit(buf, REL_HWHEEL); + } + (1, t) if t == EV_ABS => { + set_bit(buf, ABS_X); + set_bit(buf, ABS_Y); + } + _ => {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn canvas_dims_round_trip_and_clamp_to_one() { + set_canvas_dims(640, 480); + assert_eq!(canvas_dims(), (640, 480)); + set_canvas_dims(0, 0); + assert_eq!(canvas_dims(), (1, 1)); + // Restore the default so any test running in parallel that + // expects 1280×720 sees the original value. + set_canvas_dims(1280, 720); + } + + #[test] + fn evbit_type_query_kbd_advertises_syn_and_key_only() { + let mut buf = [0u8; 4]; + populate_evbit(0, 0, &mut buf); + assert_eq!(buf[0], (1 << EV_SYN) | (1 << EV_KEY)); + assert_eq!(&buf[1..], &[0, 0, 0]); + } + + #[test] + fn evbit_type_query_pointer_adds_rel_and_abs() { + let mut buf = [0u8; 4]; + populate_evbit(1, 0, &mut buf); + assert_eq!( + buf[0], + (1 << EV_SYN) | (1 << EV_KEY) | (1 << EV_REL) | (1 << EV_ABS) + ); + } + + #[test] + fn evbit_kbd_advertises_key_a_and_key_z_not_reserved() { + let mut buf = [0u8; 32]; + populate_evbit(0, EV_KEY, &mut buf); + let a_byte = (KEY_A >> 3) as usize; + let z_byte = (KEY_Z >> 3) as usize; + assert_ne!(buf[a_byte] & (1 << (KEY_A & 7)), 0); + assert_ne!(buf[z_byte] & (1 << (KEY_Z & 7)), 0); + assert_eq!(buf[0] & 1, 0, "KEY_RESERVED must not be advertised"); + } + + #[test] + fn evbit_pointer_advertises_btn_left_not_key_a() { + // BTN_LEFT = 0x110 = bit 272 → byte 34. KEY_A = 30 → byte 3. + let mut buf = [0u8; 40]; + populate_evbit(1, EV_KEY, &mut buf); + let left_byte = (BTN_LEFT >> 3) as usize; + assert_ne!(buf[left_byte] & (1 << (BTN_LEFT & 7)), 0); + let a_byte = (KEY_A >> 3) as usize; + assert_eq!(buf[a_byte] & (1 << (KEY_A & 7)), 0); + } + + #[test] + fn evbit_pointer_rel_query_advertises_wheels() { + let mut buf = [0u8; 4]; + populate_evbit(1, EV_REL, &mut buf); + assert_ne!(buf[0] & (1 << REL_X), 0); + assert_ne!(buf[0] & (1 << REL_Y), 0); + assert_ne!(buf[0] & (1 << REL_HWHEEL), 0); + assert_ne!(buf[1] & (1 << (REL_WHEEL - 8)), 0); + } + + #[test] + fn evbit_pointer_abs_query_advertises_x_and_y() { + let mut buf = [0u8; 4]; + populate_evbit(1, EV_ABS, &mut buf); + assert_eq!(buf[0], (1 << ABS_X) | (1 << ABS_Y)); + } + + #[test] + fn evbit_kbd_abs_query_is_empty() { + let mut buf = [0u8; 4]; + populate_evbit(0, EV_ABS, &mut buf); + assert_eq!(buf, [0; 4]); + } + + #[test] + fn evbit_truncates_silently_when_buf_too_small() { + // KEY_ESC fits in bit 1; KEY_A (30) falls off — no panic. + let mut buf = [0u8; 1]; + populate_evbit(0, EV_KEY, &mut buf); + assert_ne!(buf[0] & (1 << KEY_ESC), 0); + } +} diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index f245c19bad..df4ceafc08 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -17,6 +17,7 @@ pub(crate) mod exec_target; pub mod fd; pub mod fifo; pub mod fork; +pub mod input; pub mod ipc; pub(crate) mod ipc_wire; pub mod lock; diff --git a/crates/kernel/src/ofd.rs b/crates/kernel/src/ofd.rs index f3afdd3c81..48796fad7d 100644 --- a/crates/kernel/src/ofd.rs +++ b/crates/kernel/src/ofd.rs @@ -274,6 +274,48 @@ pub enum DriOfdState { Card { dri: DriFdState, kms: KmsFdState }, } +/// Per-OFD ring cap: 1024 `struct input_event` records (24 KiB). +pub const INPUT_RING_MAX_RECORDS: usize = 1024; +/// Per-OFD ring cap in bytes — `INPUT_RING_MAX_RECORDS * size_of::()`. +pub const INPUT_RING_MAX_BYTES: usize = INPUT_RING_MAX_RECORDS * 24; + +/// Per-fd state for `/dev/input/event{0,1}` opens. +/// +/// Disjoint from [`DriOfdState`] — input fds carry no DRI bo state. +/// We keep `input_state` as a separate `Option>` field on the +/// OFD rather than folding it into `DriOfdState` because the two +/// state machines have no shared invariants. +/// +/// Linux semantics replicated: +/// * The ring is per-OFD, not per-process. `dup` / fork-inherit share +/// one ring; a fresh `open()` gets a new one. +/// * On overflow, the **new** event is discarded and `dropped` is set +/// (mirrors `drivers/input/evdev.c::evdev_pass_values`). The next +/// `read()` synthesises a `SYN_DROPPED` record at the head of the +/// returned buffer and clears `dropped`; userspace is expected to +/// resynchronise by re-querying state via `EVIOCG*`. +/// * `grabbed` is recorded but NOT enforced in v1 — plan 9 +/// (wpkcompositor) adds the cross-OFD focus-routing layer. +#[derive(Default, Clone, Debug)] +pub struct InputFdState { + /// Which device this fd is bound to (0 = kbd, 1 = ptr). Cached + /// to avoid a second `VirtualDevice` lookup on read / poll. + pub device: u8, + + /// Ring of 24-byte `WpkInputEvent` records. Bounded at + /// [`INPUT_RING_MAX_BYTES`]. + pub event_ring: VecDeque, + + /// `EVIOCGRAB` ownership flag. v1 records the flag but doesn't + /// gate event delivery on it. + pub grabbed: bool, + + /// Set when an event push found the ring full; cleared on the + /// next `read()` *after* a `SYN_DROPPED` synthetic record is + /// delivered at the head of that read's output. + pub dropped: bool, +} + #[derive(Clone)] pub struct OpenFileDesc { /// Machine-wide identity of this open file description. Independent @@ -312,6 +354,10 @@ pub struct OpenFileDesc { /// DRI sidecar; see [`DriOfdState`]. Boxed so non-DRI OFDs pay /// only one pointer slot. pub dri_state: Option>, + /// evdev sidecar for `/dev/input/event{0,1}` OFDs; see + /// [`InputFdState`]. Boxed so non-evdev OFDs pay only one pointer + /// slot. Parallel to [`Self::dri_state`] (disjoint state machines). + pub input_state: Option>, } struct SharedOfdStateInner { @@ -536,6 +582,17 @@ impl OpenFileDesc { _ => None, } } + + /// Borrow the `InputFdState` for `/dev/input/event{0,1}` OFDs. + /// Returns `None` for any other OFD. + pub fn input(&self) -> Option<&InputFdState> { + self.input_state.as_deref() + } + + pub fn input_mut(&mut self) -> Option<&mut InputFdState> { + self.input_state.as_deref_mut() + } + } #[derive(Clone)] @@ -573,6 +630,7 @@ impl OfdTable { dir_position_generation: 0, dir_pending_entry: None, dri_state: None, + input_state: None, }; self.insert(ofd) @@ -612,6 +670,7 @@ impl OfdTable { dir_position_generation: 0, dir_pending_entry: None, dri_state: None, + input_state: None, }; ofd.reset_directory_iterator_for_reopen(); self.insert(ofd) @@ -962,6 +1021,7 @@ mod tests { dir_position_generation: 0, dir_pending_entry: None, dri_state: None, + input_state: None, }); } @@ -1164,6 +1224,42 @@ mod tests { assert!(table.get(render).unwrap().dri_state.is_some()); } + #[test] + fn ofd_default_has_no_input_state() { + let mut table = OfdTable::new(); + let idx = table.create(FileType::CharDevice, O_RDONLY, -10, b"/dev/input/event0".to_vec()); + let ofd = table.get(idx).unwrap(); + assert!(ofd.input_state.is_none()); + assert!(ofd.input().is_none()); + } + + #[test] + fn input_accessors_route_to_attached_state() { + let mut table = OfdTable::new(); + let idx = table.create(FileType::CharDevice, O_RDONLY, -10, b"/dev/input/event0".to_vec()); + table.get_mut(idx).unwrap().input_state = Some(Box::new(InputFdState { + device: 0, + ..Default::default() + })); + + let st = table.get(idx).unwrap().input().unwrap(); + assert_eq!(st.device, 0); + assert!(!st.grabbed); + assert!(!st.dropped); + assert!(st.event_ring.is_empty()); + + let st = table.get_mut(idx).unwrap().input_mut().unwrap(); + st.event_ring.push_back(0xab); + assert_eq!(table.get(idx).unwrap().input().unwrap().event_ring.len(), 1); + } + + #[test] + fn input_ring_cap_bytes_is_24_kib() { + // Lock the per-fd memory budget so it cannot drift silently. + assert_eq!(INPUT_RING_MAX_RECORDS, 1024); + assert_eq!(INPUT_RING_MAX_BYTES, 24 * 1024); + } + #[test] fn iter_mut_visits_every_live_ofd() { let mut table = OfdTable::new(); diff --git a/crates/kernel/src/process.rs b/crates/kernel/src/process.rs index fe44ae2caf..99684130a0 100644 --- a/crates/kernel/src/process.rs +++ b/crates/kernel/src/process.rs @@ -2494,7 +2494,30 @@ pub(crate) mod test_host { } fn unbind_framebuffer(&mut self, _p: i32) {} fn fb_write(&mut self, _p: i32, _o: usize, _b: &[u8]) {} + + /// Test-only hook: when [`PROC_READ_SOURCE`] holds a non-empty + /// buffer, copy it (up to `dst.len()`) into `dst`. Otherwise + /// behaves like the trait default (returns 0, leaves `dst` + /// untouched). Lets tests for kernel paths that copy user + /// memory (e.g. `WRITEI_FRAMES`) drive byte content without a + /// bespoke `HostIO` impl. + fn proc_read_bytes(&mut self, _pid: i32, _addr: u32, dst: &mut [u8]) -> i32 { + let src = PROC_READ_SOURCE.lock().unwrap_or_else(|e| e.into_inner()); + let n = dst.len().min(src.len()); + if n > 0 { + dst[..n].copy_from_slice(&src[..n]); + } + 0 + } } + + /// Source buffer for [`NoopHost::proc_read_bytes`]. Empty by + /// default; tests that need to drive byte content into a kernel + /// path overwrite it under the relevant subsystem's serialization + /// lock (`audio::sab::TEST_SAB_LOCK`, etc.) and reset it back to + /// empty before releasing the lock. + pub static PROC_READ_SOURCE: std::sync::Mutex> = + std::sync::Mutex::new(alloc::vec::Vec::new()); } #[cfg(test)] diff --git a/crates/kernel/src/process_table.rs b/crates/kernel/src/process_table.rs index 657193c2f7..be2246a9a5 100644 --- a/crates/kernel/src/process_table.rs +++ b/crates/kernel/src/process_table.rs @@ -1704,6 +1704,17 @@ impl ProcessTable { } } +/// Run `f` over every live process. The audio period tick and the evdev +/// fan-out both need to reach each process's OFD table from outside a +/// syscall, where no `&mut Process` is in scope. +pub fn with_processes(f: F) +where + F: FnOnce(alloc::collections::btree_map::ValuesMut<'_, u32, Process>), +{ + let table = unsafe { &mut *GLOBAL_PROCESS_TABLE.0.get() }; + f(table.processes.values_mut()); +} + #[cfg(test)] mod wait_tests { use super::*; diff --git a/crates/kernel/src/syscalls.rs b/crates/kernel/src/syscalls.rs index 729d01ef2e..d03ba6956c 100644 --- a/crates/kernel/src/syscalls.rs +++ b/crates/kernel/src/syscalls.rs @@ -159,6 +159,10 @@ pub enum VirtualDevice { Dsp, // /dev/dsp host_handle = -7 DriRenderD128, // /dev/dri/renderD128 host_handle = -8 DriCard0, // /dev/dri/card0 host_handle = -9 + /// `/dev/input/event{0,1}`. `device = 0` → kbd (host_handle -10), + /// `device = 1` → ptr (host_handle -11). v1 exposes exactly these + /// two; `/dev/input/eventN` for N≥2 is not synthesised. + InputEvent { device: u8 }, } impl VirtualDevice { @@ -174,6 +178,7 @@ impl VirtualDevice { VirtualDevice::Dsp => -7, VirtualDevice::DriRenderD128 => -8, VirtualDevice::DriCard0 => -9, + VirtualDevice::InputEvent { device } => -10 - device as i64, } } @@ -189,6 +194,8 @@ impl VirtualDevice { -7 => Some(VirtualDevice::Dsp), -8 => Some(VirtualDevice::DriRenderD128), -9 => Some(VirtualDevice::DriCard0), + -10 => Some(VirtualDevice::InputEvent { device: 0 }), + -11 => Some(VirtualDevice::InputEvent { device: 1 }), _ => None, } } @@ -205,6 +212,7 @@ impl VirtualDevice { VirtualDevice::Dsp => 7, VirtualDevice::DriRenderD128 => 8, VirtualDevice::DriCard0 => 9, + VirtualDevice::InputEvent { device } => 10 + device as u64, } } } @@ -226,6 +234,8 @@ fn match_virtual_device(path: &[u8]) -> Option { b"/dev/dsp" => Some(VirtualDevice::Dsp), b"/dev/dri/renderD128" => Some(VirtualDevice::DriRenderD128), b"/dev/dri/card0" => Some(VirtualDevice::DriCard0), + b"/dev/input/event0" => Some(VirtualDevice::InputEvent { device: 0 }), + b"/dev/input/event1" => Some(VirtualDevice::InputEvent { device: 1 }), _ => None, } } @@ -728,6 +738,19 @@ fn install_dri_state_on_open(proc: &mut Process, ofd_idx: usize, dev: VirtualDev } } +/// Install the evdev sidecar on a freshly-allocated OFD for a +/// `/dev/input/event{0,1}` open. No-op for any other virtual device. +fn install_input_state_on_open(proc: &mut Process, ofd_idx: usize, dev: VirtualDevice) { + if let VirtualDevice::InputEvent { device } = dev { + if let Some(ofd) = proc.ofd_table.get_mut(ofd_idx) { + ofd.input_state = Some(alloc::boxed::Box::new(crate::ofd::InputFdState { + device, + ..Default::default() + })); + } + } +} + /// Borrow the `DriFdState` hung off the OFD at `ofd_idx`, returning /// `EBADF` if the OFD doesn't have one or is a prime-bo. Used by /// renderD128- and card0-targeted ioctls that manipulate per-fd GEM @@ -981,6 +1004,26 @@ fn commit_exec_state_impl( Ok(()) } +fn input_state( + proc: &Process, + ofd_idx: usize, +) -> Result<&crate::ofd::InputFdState, Errno> { + proc.ofd_table + .get(ofd_idx) + .and_then(|o| o.input()) + .ok_or(Errno::EBADF) +} + +fn input_state_mut( + proc: &mut Process, + ofd_idx: usize, +) -> Result<&mut crate::ofd::InputFdState, Errno> { + proc.ofd_table + .get_mut(ofd_idx) + .and_then(|o| o.input_mut()) + .ok_or(Errno::EBADF) +} + /// Release a per-fd handle (DESTROY_DUMB / GEM_CLOSE): drops the /// handle from the fd's namespace, decrefs the bo, and if the /// refcount hits zero asks the host to free the backing. @@ -1844,6 +1887,122 @@ fn handle_dri_card_ioctl( } } +/// `EVIOCG*` ioctl surface for `/dev/input/event{0,1}`. Unknown +/// requests return `ENOTTY` (not `EINVAL`) so SDL2's evdev probe keeps +/// walking instead of fataling on the first unsupported call. +fn handle_input_ioctl( + proc: &mut Process, + ofd_idx: usize, + request: u32, + buf: &mut [u8], +) -> Result<(), Errno> { + use wasm_posix_shared::input::*; + + let dir = (request >> 30) & 0x3; + let magic = (request >> 8) & 0xff; + let nr = request & 0xff; + let size = ((request >> 16) & 0x3fff) as usize; + + if magic != b'E' as u32 { + return Err(Errno::ENOTTY); + } + + match nr { + 0x01 if dir == 2 => { + if buf.len() < 4 { + return Err(Errno::EINVAL); + } + let version: u32 = 0x0001_0001; + buf[0..4].copy_from_slice(&version.to_le_bytes()); + Ok(()) + } + 0x02 if dir == 2 => { + if buf.len() < core::mem::size_of::() { + return Err(Errno::EINVAL); + } + let device = input_state(proc, ofd_idx)?.device; + let id = WpkInputId { + bustype: BUS_VIRTUAL, + vendor: 0x1209, + product: if device == 0 { 0x0001 } else { 0x0002 }, + version: 0x0001, + }; + unsafe { + core::ptr::write_unaligned(buf.as_mut_ptr() as *mut WpkInputId, id); + } + Ok(()) + } + n if n == EVIOCGNAME_NR && dir == 2 => { + let device = input_state(proc, ofd_idx)?.device; + let name: &[u8] = if device == 0 { + b"wpk virtual keyboard\0" + } else { + b"wpk virtual pointer\0" + }; + let copy_len = name.len().min(size).min(buf.len()); + buf[..copy_len].copy_from_slice(&name[..copy_len]); + Ok(()) + } + n if (EVIOCGBIT_NR_BASE..EVIOCGBIT_NR_BASE + 32).contains(&n) && dir == 2 => { + let ev_type = (n - EVIOCGBIT_NR_BASE) as u16; + let device = input_state(proc, ofd_idx)?.device; + let len = size.min(buf.len()); + let slice = &mut buf[..len]; + for b in slice.iter_mut() { + *b = 0; + } + crate::input::populate_evbit(device, ev_type, slice); + Ok(()) + } + n if (EVIOCGABS_NR_BASE..EVIOCGABS_NR_BASE + 64).contains(&n) && dir == 2 => { + if buf.len() < core::mem::size_of::() { + return Err(Errno::EINVAL); + } + let axis = (n - EVIOCGABS_NR_BASE) as u16; + let device = input_state(proc, ofd_idx)?.device; + if device != 1 { + return Err(Errno::ENOTTY); + } + let (w, h) = crate::input::canvas_dims(); + let abs = match axis { + ABS_X => WpkInputAbsinfo { + value: 0, + minimum: 0, + maximum: (w as i32) - 1, + fuzz: 0, + flat: 0, + resolution: 1, + }, + ABS_Y => WpkInputAbsinfo { + value: 0, + minimum: 0, + maximum: (h as i32) - 1, + fuzz: 0, + flat: 0, + resolution: 1, + }, + _ => return Err(Errno::ENOTTY), + }; + unsafe { + core::ptr::write_unaligned( + buf.as_mut_ptr() as *mut WpkInputAbsinfo, + abs, + ); + } + Ok(()) + } + 0x90 if dir == 1 => { + if buf.len() < 4 { + return Err(Errno::EINVAL); + } + let value = i32::from_le_bytes(buf[..4].try_into().unwrap()); + input_state_mut(proc, ofd_idx)?.grabbed = value != 0; + Ok(()) + } + _ => Err(Errno::ENOTTY), + } +} + /// Run DRI-specific cleanup for a freshly-freed OFD: release the /// per-fd GEM handle map (decref every bo, free host backing on the /// last drop) and release a prime-bo capability cookie if any. @@ -3070,6 +3229,7 @@ pub fn sys_open( resolved, ); install_dri_state_on_open(proc, ofd_idx, dev); + install_input_state_on_open(proc, ofd_idx, dev); let fd_flags = oflags_to_fd_flags(oflags); let fd = proc.fd_table.alloc(OpenFileDescRef(ofd_idx), fd_flags)?; return Ok(fd); @@ -3655,6 +3815,7 @@ fn release_ofd_reference_impl( } }; + let freed = proc.ofd_table.dec_ref(idx); if freed { @@ -4539,6 +4700,59 @@ pub fn sys_read( VirtualDevice::Null | VirtualDevice::Fb0 | VirtualDevice::DriRenderD128 => 0, // Real DSP descriptors use PcmPlayback and O_WRONLY. VirtualDevice::Dsp => return Err(Errno::EBADF), + VirtualDevice::InputEvent { .. } => { + use wasm_posix_shared::clock::CLOCK_MONOTONIC; + use wasm_posix_shared::input::{ + EV_SYN, SYN_DROPPED, WpkInputEvent, + }; + let usable = (buf.len() / 24) * 24; + if usable == 0 { + return Err(Errno::EINVAL); + } + let input = input_state_mut(proc, ofd_idx)?; + // Blocking read returns Ok(0) (not park) + // so the host can retry on a poll timer + // — matches DriCard0. + if input.event_ring.is_empty() && !input.dropped { + if status_flags & O_NONBLOCK != 0 { + return Err(Errno::EAGAIN); + } + return Ok(0); + } + let mut written = 0; + // Producer overflowed: prepend SYN_DROPPED + // so userspace resyncs via EVIOCG* before + // consuming the next real record. + if input.dropped { + let (sec, nsec) = host + .host_clock_gettime(CLOCK_MONOTONIC) + .unwrap_or((0, 0)); + let synth = WpkInputEvent { + tv_sec: sec, + tv_usec: (nsec / 1_000) as i32, + _pad: 0, + ev_type: EV_SYN, + code: SYN_DROPPED, + value: 0, + }; + let bytes: [u8; 24] = unsafe { + core::mem::transmute(synth) + }; + buf[..24].copy_from_slice(&bytes); + written = 24; + input.dropped = false; + } + while written + 24 <= usable + && !input.event_ring.is_empty() + { + for i in 0..24 { + buf[written + i] = + input.event_ring.pop_front().unwrap(); + } + written += 24; + } + written + } VirtualDevice::DriCard0 => { // Drain queued DRM events (DRM_EVENT_FLIP_COMPLETE) // into the caller buffer, one byte at a time so a @@ -13530,6 +13744,49 @@ fn poll_check(proc: &mut Process, host: &mut dyn HostIO, fds: &mut [WasmPollFd]) revents |= POLLIN; } // Mice doesn't accept writes — never report POLLOUT. + } else if ofd.file_type == FileType::CharDevice + && VirtualDevice::from_host_handle(ofd.host_handle) == Some(VirtualDevice::Dsp) + { + // /dev/dsp is write-only. POLLOUT is always ready — + // the ring drops oldest frames on overflow rather + // than blocking — and POLLIN never fires. + if pollfd.events & POLLOUT != 0 { + revents |= POLLOUT; + } + } else if ofd.file_type == FileType::CharDevice + && VirtualDevice::from_host_handle(ofd.host_handle) + == Some(VirtualDevice::DriCard0) + { + // /dev/dri/card0 gates POLLIN on the per-fd + // `event_ring` actually holding a DRM event. + // sys_read returns Ok(0) on an empty ring rather + // than blocking, so reporting always-ready POLLIN + // would race the vblank pump: poll → read → 0 → + // drmHandleEvent reports a short read and fails. + if pollfd.events & POLLIN != 0 { + if let Some(kms) = ofd.kms() { + if !kms.event_ring.is_empty() { + revents |= POLLIN; + } + } + } + // card0 doesn't accept writes — never report POLLOUT. + } else if ofd.file_type == FileType::CharDevice + && matches!( + VirtualDevice::from_host_handle(ofd.host_handle), + Some(VirtualDevice::InputEvent { .. }) + ) + { + // Gate POLLIN on the ring or the SYN_DROPPED latch: + // always-ready would spin libinput against an empty + // ring (sys_read returns Ok(0), not a record). + if pollfd.events & POLLIN != 0 { + if let Some(input) = ofd.input() { + if !input.event_ring.is_empty() || input.dropped { + revents |= POLLIN; + } + } + } } else { // Regular files and char devices are always ready if pollfd.events & POLLIN != 0 { @@ -13925,6 +14182,7 @@ pub fn sys_openat( resolved, ); install_dri_state_on_open(proc, ofd_idx, dev); + install_input_state_on_open(proc, ofd_idx, dev); let fd_flags = oflags_to_fd_flags(oflags); let fd = proc.fd_table.alloc(OpenFileDescRef(ofd_idx), fd_flags)?; return Ok(fd); @@ -14585,6 +14843,18 @@ pub fn sys_ioctl( } } + // --- /dev/input/event{0,1} ioctls — evdev EVIOCG* surface --- + { + let ofd = proc.ofd_table.get(ofd_idx).ok_or(Errno::EBADF)?; + if ofd.file_type == FileType::CharDevice { + if let Some(VirtualDevice::InputEvent { .. }) = + VirtualDevice::from_host_handle(ofd.host_handle) + { + return handle_input_ioctl(proc, ofd_idx, request, buf); + } + } + } + // --- Linux VT keyboard ioctls (KDGKBTYPE / KDGKBMODE / KDSKBMODE) --- // // fbDOOM (and other Linux-VT-targeted software) calls these on a @@ -34823,29 +35093,6 @@ mod tests { assert_eq!(match_dev_fd(b"/tmp/foo"), None); } - #[test] - fn test_virtual_device_roundtrip() { - for dev in [ - VirtualDevice::Null, - VirtualDevice::Zero, - VirtualDevice::Urandom, - VirtualDevice::Full, - VirtualDevice::Fb0, - VirtualDevice::Mice, - VirtualDevice::Dsp, - VirtualDevice::DriRenderD128, - VirtualDevice::DriCard0, - ] { - assert_eq!( - VirtualDevice::from_host_handle(dev.host_handle()), - Some(dev) - ); - } - assert_eq!(VirtualDevice::from_host_handle(0), None); - // First sentinel past the allocated range — must not roundtrip. - assert_eq!(VirtualDevice::from_host_handle(-10), None); - } - // ===== Loopback socket tests ===== #[test] @@ -40230,8 +40477,6 @@ mod tests { match_virtual_device(b"/dev/input/mice"), Some(VirtualDevice::Mice) ); - // No /dev/input/event0 — evdev is out of scope for v1. - assert_eq!(match_virtual_device(b"/dev/input/event0"), None); } #[test] @@ -45028,4 +45273,557 @@ mod tests { assert_eq!(proc.exec_generation, 0); assert_eq!(host.closed_handles, vec![100]); } + + // ----------------------------------------------------------------- + // /dev/input/event{0,1} tests — A2 surface (open + OFD wiring). + // Read/poll drain semantics land in A5; ioctl dispatch in A3. + // ----------------------------------------------------------------- + + #[test] + fn match_virtual_device_recognizes_evdev_nodes() { + assert_eq!( + match_virtual_device(b"/dev/input/event0"), + Some(VirtualDevice::InputEvent { device: 0 }) + ); + assert_eq!( + match_virtual_device(b"/dev/input/event1"), + Some(VirtualDevice::InputEvent { device: 1 }) + ); + // event2+ deliberately not synthesised. + assert_eq!(match_virtual_device(b"/dev/input/event2"), None); + assert_eq!(match_virtual_device(b"/dev/input/event10"), None); + } + + #[test] + fn open_event0_yields_input_state_with_device_zero() { + let mut proc = Process::new(101); + let mut host = MockHostIO::new(); + let fd = sys_open(&mut proc, &mut host, b"/dev/input/event0", O_RDWR, 0).unwrap(); + let entry = proc.fd_table.get(fd).unwrap(); + let ofd = proc.ofd_table.get(entry.ofd_ref.0).unwrap(); + let st = ofd.input().expect("input_state should be installed"); + assert_eq!(st.device, 0); + assert!(!st.grabbed); + assert!(!st.dropped); + assert!(st.event_ring.is_empty()); + // input + dri sidecars are disjoint state machines. + assert!(ofd.dri_state.is_none()); + } + + #[test] + fn open_event1_yields_input_state_with_device_one() { + let mut proc = Process::new(102); + let mut host = MockHostIO::new(); + let fd = sys_open(&mut proc, &mut host, b"/dev/input/event1", O_RDWR, 0).unwrap(); + let entry = proc.fd_table.get(fd).unwrap(); + let st = proc + .ofd_table + .get(entry.ofd_ref.0) + .and_then(|o| o.input()) + .expect("input_state should be installed"); + assert_eq!(st.device, 1); + } + + #[test] + fn open_event0_is_multi_process_no_busy() { + // Unlike /dev/fb0 + /dev/input/mice + /dev/dsp (single-owner), + // evdev nodes accept multiple opens — every process can + // attach its own ring. + let mut proc1 = Process::new(201); + let mut proc2 = Process::new(202); + let mut host = MockHostIO::new(); + assert!(sys_open(&mut proc1, &mut host, b"/dev/input/event0", O_RDONLY, 0).is_ok()); + assert!(sys_open(&mut proc2, &mut host, b"/dev/input/event0", O_RDONLY, 0).is_ok()); + } + + #[test] + fn open_nonexistent_event_path_returns_enoent() { + let mut proc = Process::new(301); + let mut host = MockHostIO::new(); + let r = sys_open(&mut proc, &mut host, b"/dev/input/event2", O_RDONLY, 0); + assert!(r.is_err(), "/dev/input/event2 must NOT open as a virtual device"); + } + + #[test] + fn read_eventN_returns_zero_before_any_event() { + let mut proc = Process::new(401); + let mut host = MockHostIO::new(); + let fd = sys_open(&mut proc, &mut host, b"/dev/input/event0", O_RDONLY, 0).unwrap(); + let mut buf = [0u8; 24]; + let n = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap(); + assert_eq!(n, 0); + } + + const fn evioc(dir: u32, nr: u32, size: u32) -> u32 { + (dir << 30) | (size << 16) | ((b'E' as u32) << 8) | nr + } + + fn open_evdev(pid: u32, path: &[u8]) -> (Process, MockHostIO, i32) { + let mut proc = Process::new(pid); + let mut host = MockHostIO::new(); + let fd = sys_open(&mut proc, &mut host, path, O_RDWR, 0).unwrap(); + (proc, host, fd) + } + + #[test] + fn evioc_gversion_returns_010001() { + use wasm_posix_shared::input::EVIOCGVERSION; + let (mut proc, mut host, fd) = open_evdev(601, b"/dev/input/event0"); + let mut buf = [0u8; 4]; + sys_ioctl(&mut proc, &mut host, fd, EVIOCGVERSION, &mut buf).unwrap(); + assert_eq!(u32::from_le_bytes(buf), 0x0001_0001); + } + + #[test] + fn evioc_gid_keyboard_vs_pointer_differs_by_product() { + use wasm_posix_shared::input::{EVIOCGID, WpkInputId, BUS_VIRTUAL}; + let (mut proc, mut host, kfd) = open_evdev(602, b"/dev/input/event0"); + let pfd = sys_open(&mut proc, &mut host, b"/dev/input/event1", O_RDWR, 0).unwrap(); + let mut kbuf = [0u8; core::mem::size_of::()]; + sys_ioctl(&mut proc, &mut host, kfd, EVIOCGID, &mut kbuf).unwrap(); + let kid: WpkInputId = unsafe { core::ptr::read_unaligned(kbuf.as_ptr() as *const _) }; + let mut pbuf = [0u8; core::mem::size_of::()]; + sys_ioctl(&mut proc, &mut host, pfd, EVIOCGID, &mut pbuf).unwrap(); + let pid_: WpkInputId = unsafe { core::ptr::read_unaligned(pbuf.as_ptr() as *const _) }; + assert_eq!(kid.bustype, BUS_VIRTUAL); + assert_eq!(pid_.bustype, BUS_VIRTUAL); + assert_eq!(kid.vendor, pid_.vendor, "vendor matches across devices"); + assert_ne!(kid.product, pid_.product, "product distinguishes kbd vs ptr"); + assert_eq!(kid.product, 0x0001); + assert_eq!(pid_.product, 0x0002); + } + + #[test] + fn evioc_gname_event0_returns_keyboard_string() { + use wasm_posix_shared::input::EVIOCGNAME_NR; + let (mut proc, mut host, fd) = open_evdev(603, b"/dev/input/event0"); + let mut buf = [0u8; 64]; + let req = evioc(2, EVIOCGNAME_NR, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + let nul = buf.iter().position(|&b| b == 0).unwrap(); + assert_eq!(&buf[..nul], b"wpk virtual keyboard"); + } + + #[test] + fn evioc_gname_event1_returns_pointer_string() { + use wasm_posix_shared::input::EVIOCGNAME_NR; + let (mut proc, mut host, fd) = open_evdev(604, b"/dev/input/event1"); + let mut buf = [0u8; 64]; + let req = evioc(2, EVIOCGNAME_NR, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + let nul = buf.iter().position(|&b| b == 0).unwrap(); + assert_eq!(&buf[..nul], b"wpk virtual pointer"); + } + + #[test] + fn evioc_gname_truncates_to_caller_buffer() { + use wasm_posix_shared::input::EVIOCGNAME_NR; + let (mut proc, mut host, fd) = open_evdev(605, b"/dev/input/event0"); + // "wpk virtual keyboard" is 20 chars; a 5-byte buffer fills with + // the prefix and no NUL terminator — caller handles the cut-off. + let mut buf = [0xffu8; 5]; + let req = evioc(2, EVIOCGNAME_NR, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + assert_eq!(&buf, b"wpk v"); + } + + #[test] + fn evioc_gbit_keyboard_evtype_query_advertises_syn_and_key_only() { + use wasm_posix_shared::input::{EVIOCGBIT_NR_BASE, EV_KEY, EV_REL, EV_SYN}; + let (mut proc, mut host, fd) = open_evdev(606, b"/dev/input/event0"); + let mut buf = [0u8; 4]; + let req = evioc(2, EVIOCGBIT_NR_BASE, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + assert_ne!(buf[0] & (1 << EV_SYN), 0); + assert_ne!(buf[0] & (1 << EV_KEY), 0); + assert_eq!(buf[0] & (1 << EV_REL), 0, "keyboard must not advertise EV_REL"); + } + + #[test] + fn evioc_gbit_pointer_evtype_query_adds_rel_and_abs() { + use wasm_posix_shared::input::{EVIOCGBIT_NR_BASE, EV_ABS, EV_REL}; + let (mut proc, mut host, fd) = open_evdev(607, b"/dev/input/event1"); + let mut buf = [0u8; 4]; + let req = evioc(2, EVIOCGBIT_NR_BASE, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + assert_ne!(buf[0] & (1 << EV_REL), 0); + assert_ne!(buf[0] & (1 << EV_ABS), 0); + } + + #[test] + fn evioc_gbit_keyboard_ev_key_lists_key_a() { + use wasm_posix_shared::input::{EVIOCGBIT_NR_BASE, EV_KEY, KEY_A}; + let (mut proc, mut host, fd) = open_evdev(608, b"/dev/input/event0"); + let mut buf = [0u8; 32]; + let req = evioc(2, EVIOCGBIT_NR_BASE + EV_KEY as u32, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap(); + let byte = (KEY_A >> 3) as usize; + assert_ne!(buf[byte] & (1 << (KEY_A & 7)), 0); + } + + #[test] + fn evioc_gabs_keyboard_returns_enotty() { + use wasm_posix_shared::input::{EVIOCGABS_NR_BASE, ABS_X, WpkInputAbsinfo}; + let (mut proc, mut host, fd) = open_evdev(609, b"/dev/input/event0"); + let mut buf = [0u8; core::mem::size_of::()]; + let req = evioc(2, EVIOCGABS_NR_BASE + ABS_X as u32, buf.len() as u32); + let err = sys_ioctl(&mut proc, &mut host, fd, req, &mut buf).unwrap_err(); + // ENOTTY (not EINVAL) — SDL2 greps the errno; EINVAL fatals it. + assert_eq!(err, Errno::ENOTTY); + } + + #[test] + fn evioc_gabs_pointer_x_returns_canvas_width_minus_one() { + use wasm_posix_shared::input::{EVIOCGABS_NR_BASE, ABS_X, ABS_Y, WpkInputAbsinfo}; + crate::input::set_canvas_dims(800, 600); + let (mut proc, mut host, fd) = open_evdev(610, b"/dev/input/event1"); + let mut buf = [0u8; core::mem::size_of::()]; + let req_x = evioc(2, EVIOCGABS_NR_BASE + ABS_X as u32, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req_x, &mut buf).unwrap(); + let abs: WpkInputAbsinfo = unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const _) }; + assert_eq!(abs.maximum, 799); + assert_eq!(abs.resolution, 1); + assert_eq!(abs.minimum, 0); + let req_y = evioc(2, EVIOCGABS_NR_BASE + ABS_Y as u32, buf.len() as u32); + sys_ioctl(&mut proc, &mut host, fd, req_y, &mut buf).unwrap(); + let aby: WpkInputAbsinfo = unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const _) }; + assert_eq!(aby.maximum, 599); + // Restore the default so other tests running in parallel see + // the boot value. + crate::input::set_canvas_dims(1280, 720); + } + + #[test] + fn evioc_grab_sets_flag_then_release_clears_it() { + use wasm_posix_shared::input::EVIOCGRAB; + let (mut proc, mut host, fd) = open_evdev(611, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + let mut on = 1i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd, EVIOCGRAB, &mut on).unwrap(); + assert!(proc.ofd_table.get(ofd_idx).unwrap().input().unwrap().grabbed); + let mut off = 0i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd, EVIOCGRAB, &mut off).unwrap(); + assert!(!proc.ofd_table.get(ofd_idx).unwrap().input().unwrap().grabbed); + } + + #[test] + fn evioc_grab_twice_from_same_fd_is_idempotent() { + use wasm_posix_shared::input::EVIOCGRAB; + let (mut proc, mut host, fd) = open_evdev(612, b"/dev/input/event0"); + let mut on = 1i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd, EVIOCGRAB, &mut on).unwrap(); + let mut on2 = 1i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd, EVIOCGRAB, &mut on2).unwrap(); + } + + #[test] + fn evioc_grab_release_without_prior_grab_is_a_noop() { + use wasm_posix_shared::input::EVIOCGRAB; + let (mut proc, mut host, fd) = open_evdev(613, b"/dev/input/event0"); + let mut off = 0i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd, EVIOCGRAB, &mut off).unwrap(); + } + + #[test] + fn close_releases_grab_so_next_open_can_grab() { + use wasm_posix_shared::input::EVIOCGRAB; + let (mut proc, mut host, fd_a) = open_evdev(616, b"/dev/input/event0"); + let idx_a = proc.fd_table.get(fd_a).unwrap().ofd_ref.0; + let mut on = 1i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd_a, EVIOCGRAB, &mut on).unwrap(); + assert!(proc.ofd_table.get(idx_a).unwrap().input().unwrap().grabbed); + + sys_close(&mut proc, &mut host, fd_a).unwrap(); + assert!(proc.ofd_table.get(idx_a).is_none()); + + let fd_b = sys_open( + &mut proc, + &mut host, + b"/dev/input/event0", + O_RDWR, + 0, + ) + .unwrap(); + let idx_b = proc.fd_table.get(fd_b).unwrap().ofd_ref.0; + let input = proc.ofd_table.get(idx_b).unwrap().input().unwrap(); + assert!(input.event_ring.is_empty()); + assert!(!input.dropped); + assert!(!input.grabbed); + let mut on2 = 1i32.to_le_bytes(); + sys_ioctl(&mut proc, &mut host, fd_b, EVIOCGRAB, &mut on2).unwrap(); + assert!(proc.ofd_table.get(idx_b).unwrap().input().unwrap().grabbed); + } + + #[test] + fn fork_then_close_in_child_keeps_grab_on_parent() { + use wasm_posix_shared::input::{ + EVIOCGRAB, EV_KEY, EV_SYN, KEY_A, SYN_REPORT, + }; + let (mut parent, mut host, parent_fd) = + open_evdev(617, b"/dev/input/event0"); + let ofd_idx = parent.fd_table.get(parent_fd).unwrap().ofd_ref.0; + let mut on = 1i32.to_le_bytes(); + sys_ioctl(&mut parent, &mut host, parent_fd, EVIOCGRAB, &mut on) + .unwrap(); + push_event_into_ofd(&mut parent, ofd_idx, EV_KEY, KEY_A, 1); + push_event_into_ofd(&mut parent, ofd_idx, EV_SYN, SYN_REPORT, 0); + + let mut buf = alloc::vec![0u8; 64 * 1024]; + let written = + crate::fork::serialize_fork_state(&parent, &mut buf).unwrap(); + let mut child = + crate::fork::deserialize_fork_state(&buf[..written], 717).unwrap(); + + let child_input = child.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert_eq!(child_input.device, 0); + assert!(child_input.grabbed); + assert_eq!(child_input.event_ring.len(), 48); + + sys_close(&mut child, &mut host, parent_fd).unwrap(); + assert!(child.ofd_table.get(ofd_idx).is_none()); + + let parent_input = + parent.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert!(parent_input.grabbed); + assert_eq!(parent_input.event_ring.len(), 48); + } + + #[test] + fn evioc_unknown_request_returns_enotty_not_einval() { + // SDL2's evdev probe greps the errno; EINVAL fatals it. + let (mut proc, mut host, fd) = open_evdev(614, b"/dev/input/event0"); + let bogus = evioc(2, 0xfe, 0); + let mut buf = [0u8; 4]; + let err = sys_ioctl(&mut proc, &mut host, fd, bogus, &mut buf).unwrap_err(); + assert_eq!(err, Errno::ENOTTY); + } + + #[test] + fn evioc_foreign_magic_on_evdev_fd_returns_enotty() { + // ENOTTY (not EINVAL) so probing loops keep moving past a + // foreign-subsystem ioctl issued on an evdev fd. + let (mut proc, mut host, fd) = open_evdev(615, b"/dev/input/event0"); + let foreign = (2u32 << 30) | (4u32 << 16) | ((b'X' as u32) << 8) | 0x01; + let mut buf = [0u8; 4]; + let err = sys_ioctl(&mut proc, &mut host, fd, foreign, &mut buf).unwrap_err(); + assert_eq!(err, Errno::ENOTTY); + } + + /// Inject one `WpkInputEvent` into an OFD's ring without going + /// through `dispatch::push_event` — avoids registering the test + /// process in GLOBAL_PROCESS_TABLE. + fn push_event_into_ofd( + proc: &mut Process, + ofd_idx: usize, + ev_type: u16, + code: u16, + value: i32, + ) { + use wasm_posix_shared::input::WpkInputEvent; + let input = proc + .ofd_table + .get_mut(ofd_idx) + .unwrap() + .input_mut() + .unwrap(); + let ev = WpkInputEvent { + tv_sec: 0, + tv_usec: 0, + _pad: 0, + ev_type, + code, + value, + }; + let bytes: [u8; 24] = unsafe { core::mem::transmute(ev) }; + for b in bytes { + input.event_ring.push_back(b); + } + } + + fn extract_record_at( + buf: &[u8], + off: usize, + ) -> wasm_posix_shared::input::WpkInputEvent { + unsafe { + core::ptr::read_unaligned( + buf.as_ptr().add(off) as *const wasm_posix_shared::input::WpkInputEvent, + ) + } + } + + #[test] + fn read_returns_einval_for_buffer_shorter_than_one_record() { + // Linux evdev rejects sub-record reads — partial returns would + // break the input_event boundary contract. + let (mut proc, mut host, fd) = open_evdev(701, b"/dev/input/event0"); + let mut buf = [0u8; 12]; + let err = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap_err(); + assert_eq!(err, Errno::EINVAL); + } + + #[test] + fn read_drains_whole_records_from_ring() { + use wasm_posix_shared::input::{EV_KEY, EV_SYN, KEY_A, SYN_REPORT}; + let (mut proc, mut host, fd) = open_evdev(702, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, 1); + push_event_into_ofd(&mut proc, ofd_idx, EV_SYN, SYN_REPORT, 0); + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, 0); + let mut buf = [0u8; 72]; + let n = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap(); + assert_eq!(n, 72); + let r0 = extract_record_at(&buf, 0); + let r1 = extract_record_at(&buf, 24); + let r2 = extract_record_at(&buf, 48); + assert_eq!((r0.ev_type, r0.code, r0.value), (EV_KEY, KEY_A, 1)); + assert_eq!((r1.ev_type, r1.code, r1.value), (EV_SYN, SYN_REPORT, 0)); + assert_eq!((r2.ev_type, r2.code, r2.value), (EV_KEY, KEY_A, 0)); + let input = proc.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert!(input.event_ring.is_empty()); + } + + #[test] + fn read_truncates_to_whole_record_boundary_and_leaves_remainder() { + use wasm_posix_shared::input::{EV_KEY, KEY_A}; + let (mut proc, mut host, fd) = open_evdev(703, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + for v in 0..3 { + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, v); + } + // 50 floors to 48 (= 2 records); one stays in the ring. + let mut buf = [0u8; 50]; + let n = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap(); + assert_eq!(n, 48); + let r0 = extract_record_at(&buf, 0); + let r1 = extract_record_at(&buf, 24); + assert_eq!(r0.value, 0); + assert_eq!(r1.value, 1); + let input = proc.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert_eq!(input.event_ring.len(), 24); + let mut buf2 = [0u8; 24]; + let n2 = sys_read(&mut proc, &mut host, fd, &mut buf2).unwrap(); + assert_eq!(n2, 24); + assert_eq!(extract_record_at(&buf2, 0).value, 2); + } + + #[test] + fn read_with_dropped_flag_emits_syn_dropped_and_clears_flag() { + use wasm_posix_shared::input::{EV_SYN, SYN_DROPPED}; + let (mut proc, mut host, fd) = open_evdev(704, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + proc.ofd_table + .get_mut(ofd_idx) + .unwrap() + .input_mut() + .unwrap() + .dropped = true; + let mut buf = [0u8; 24]; + let n = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap(); + assert_eq!(n, 24); + let synth = extract_record_at(&buf, 0); + assert_eq!(synth.ev_type, EV_SYN); + assert_eq!(synth.code, SYN_DROPPED); + assert_eq!(synth.value, 0); + let input = proc.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert!(!input.dropped, "dropped flag must clear after SYN_DROPPED emit"); + } + + #[test] + fn read_after_overflow_emits_syn_dropped_then_real_records() { + use wasm_posix_shared::input::{EV_KEY, EV_SYN, KEY_A, SYN_DROPPED}; + let (mut proc, mut host, fd) = open_evdev(705, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, 100); + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, 101); + // dispatch::push_event would latch `dropped` on a full ring — + // simulate that here without running the producer. + proc.ofd_table + .get_mut(ofd_idx) + .unwrap() + .input_mut() + .unwrap() + .dropped = true; + let mut buf = [0u8; 72]; + let n = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap(); + assert_eq!(n, 72); + let synth = extract_record_at(&buf, 0); + assert_eq!((synth.ev_type, synth.code), (EV_SYN, SYN_DROPPED)); + let r1 = extract_record_at(&buf, 24); + let r2 = extract_record_at(&buf, 48); + assert_eq!((r1.ev_type, r1.code, r1.value), (EV_KEY, KEY_A, 100)); + assert_eq!((r2.ev_type, r2.code, r2.value), (EV_KEY, KEY_A, 101)); + let input = proc.ofd_table.get(ofd_idx).unwrap().input().unwrap(); + assert!(!input.dropped); + assert!(input.event_ring.is_empty()); + } + + #[test] + fn read_empty_ring_with_nonblock_returns_eagain() { + let mut proc = Process::new(706); + let mut host = MockHostIO::new(); + let fd = sys_open( + &mut proc, + &mut host, + b"/dev/input/event0", + O_RDONLY | O_NONBLOCK, + 0, + ) + .unwrap(); + let mut buf = [0u8; 24]; + let err = sys_read(&mut proc, &mut host, fd, &mut buf).unwrap_err(); + assert_eq!(err, Errno::EAGAIN); + } + + #[test] + fn poll_pollin_idle_then_ready_after_event_pushed() { + use wasm_posix_shared::WasmPollFd; + use wasm_posix_shared::input::{EV_KEY, KEY_A}; + use wasm_posix_shared::poll::POLLIN; + let (mut proc, mut host, fd) = open_evdev(707, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + let mut pollfd = WasmPollFd { fd, events: POLLIN, revents: 0 }; + let n = sys_poll(&mut proc, &mut host, core::slice::from_mut(&mut pollfd), 0).unwrap(); + assert_eq!(n, 0, "empty ring + no dropped latch → POLLIN idle"); + assert_eq!(pollfd.revents, 0); + push_event_into_ofd(&mut proc, ofd_idx, EV_KEY, KEY_A, 1); + let mut pollfd = WasmPollFd { fd, events: POLLIN, revents: 0 }; + let n = sys_poll(&mut proc, &mut host, core::slice::from_mut(&mut pollfd), 0).unwrap(); + assert_eq!(n, 1); + assert_ne!(pollfd.revents & POLLIN, 0); + } + + #[test] + fn poll_pollin_ready_when_only_dropped_flag_is_set() { + // The SYN_DROPPED marker alone is a readable 24-byte record; + // poll must fire even with an empty ring. + use wasm_posix_shared::WasmPollFd; + use wasm_posix_shared::poll::POLLIN; + let (mut proc, mut host, fd) = open_evdev(708, b"/dev/input/event0"); + let ofd_idx = proc.fd_table.get(fd).unwrap().ofd_ref.0; + proc.ofd_table + .get_mut(ofd_idx) + .unwrap() + .input_mut() + .unwrap() + .dropped = true; + let mut pollfd = WasmPollFd { fd, events: POLLIN, revents: 0 }; + let n = sys_poll(&mut proc, &mut host, core::slice::from_mut(&mut pollfd), 0).unwrap(); + assert_eq!(n, 1); + assert_ne!(pollfd.revents & POLLIN, 0); + } + + #[test] + fn poll_never_reports_pollout_for_evdev_fd() { + use wasm_posix_shared::WasmPollFd; + use wasm_posix_shared::poll::{POLLIN, POLLOUT}; + let (mut proc, mut host, fd) = open_evdev(709, b"/dev/input/event0"); + let mut pollfd = WasmPollFd { + fd, + events: POLLIN | POLLOUT, + revents: 0, + }; + let n = sys_poll(&mut proc, &mut host, core::slice::from_mut(&mut pollfd), 0).unwrap(); + assert_eq!(n, 0); + assert_eq!(pollfd.revents & POLLOUT, 0); + } } diff --git a/crates/kernel/src/wasm_api.rs b/crates/kernel/src/wasm_api.rs index 1135114a8f..b3df35d589 100644 --- a/crates/kernel/src/wasm_api.rs +++ b/crates/kernel/src/wasm_api.rs @@ -13940,6 +13940,41 @@ pub extern "C" fn kernel_vblank() -> u32 { crate::dri::vblank_tick() } +/// Fan one translated DOM input event out to every open OFD bound to +/// `/dev/input/event{0,1}`. Stamped with CLOCK_MONOTONIC so libinput / +/// SDL2 see a single monotonic timeline across vblank + input streams. +#[unsafe(no_mangle)] +pub extern "C" fn kernel_input_event( + device: u32, + ev_type: u32, + code: u32, + value: i32, +) { + let mut host = WasmHostIO; + let (tv_sec, tv_usec) = match host.host_clock_gettime( + wasm_posix_shared::clock::CLOCK_MONOTONIC, + ) { + Ok((sec, nsec)) => (sec, (nsec / 1000) as i32), + Err(_) => (0i64, 0i32), + }; + crate::input::dispatch::push_event( + device as u8, + ev_type as u16, + code as u16, + value, + tv_sec, + tv_usec, + ); +} + +/// Cache the canvas pixel dimensions advertised by +/// `EVIOCGABS(ABS_X/ABS_Y)` on `/dev/input/event1`. Without this the +/// first SDL2 / libinput probe sees the 1280×720 fallback. +#[unsafe(no_mangle)] +pub extern "C" fn kernel_set_input_canvas_dims(width: u32, height: u32) { + crate::input::set_canvas_dims(width, height); +} + /// Number of successful page-flip commits on the given crtc. /// /// Useful for the host-side stats UI ("how many frames has the diff --git a/crates/shared/src/ioctl_contract.rs b/crates/shared/src/ioctl_contract.rs index 4ad93afc76..38a7263575 100644 --- a/crates/shared/src/ioctl_contract.rs +++ b/crates/shared/src/ioctl_contract.rs @@ -138,6 +138,78 @@ macro_rules! pointer { }; } +/// How a request family derives the byte count it marshals. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IoctlFamilySize { + /// Every member marshals the same structure, so the caller's encoded + /// size must match exactly. + Fixed(u32), + /// The caller chooses the length and encodes it in the request. Bounded + /// so a malformed request cannot stage an oversized scratch buffer. + CallerEncoded { max: u32 }, +} + +/// A contiguous `nr` range that shares one marshalling contract. +/// +/// `EVIOCGNAME(len)` and `EVIOCGBIT(ev, len)` let the caller pick the buffer +/// length, so every length is a distinct request number; `EVIOCGABS(axis)` +/// keeps one structure across 64 axes. Neither shape fits a table keyed by +/// exact request number, so they resolve through `IOCTL_REQUEST_FAMILIES` +/// after `IOCTL_REQUEST_CONTRACTS` misses. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IoctlRequestFamily { + pub dir: u32, + pub magic: u32, + pub nr_first: u32, + pub nr_last: u32, + pub direction: IoctlDirection, + pub size: IoctlFamilySize, +} + +/// Largest buffer a caller-encoded request may stage. +/// +/// `EVIOCGNAME` returns a device name and `EVIOCGBIT` a capability bitmap; +/// the widest bitmap Kandelo advertises is `EV_KEY`, which needs +/// `KEY_CNT / 8` bytes. 256 covers both with headroom. +pub const EVIOC_MAX_CALLER_LENGTH: u32 = 256; + +/// Pointer ioctls whose request number varies by length or by axis. +/// +/// Ordering is not load-bearing here — lookup is a linear scan over a short +/// table, and the ranges are disjoint. +pub const IOCTL_REQUEST_FAMILIES: &[IoctlRequestFamily] = &[ + IoctlRequestFamily { + dir: 2, + magic: b'E' as u32, + nr_first: crate::input::EVIOCGNAME_NR, + nr_last: crate::input::EVIOCGNAME_NR, + direction: IoctlDirection::Out, + size: IoctlFamilySize::CallerEncoded { + max: EVIOC_MAX_CALLER_LENGTH, + }, + }, + IoctlRequestFamily { + dir: 2, + magic: b'E' as u32, + nr_first: crate::input::EVIOCGBIT_NR_BASE, + nr_last: crate::input::EVIOCGBIT_NR_BASE + 31, + direction: IoctlDirection::Out, + size: IoctlFamilySize::CallerEncoded { + max: EVIOC_MAX_CALLER_LENGTH, + }, + }, + IoctlRequestFamily { + dir: 2, + magic: b'E' as u32, + nr_first: crate::input::EVIOCGABS_NR_BASE, + nr_last: crate::input::EVIOCGABS_NR_BASE + 63, + direction: IoctlDirection::Out, + size: IoctlFamilySize::Fixed( + core::mem::size_of::() as u32, + ), + }, +]; + /// Ioctls that may reach the Rust kernel dispatcher. /// /// Keep entries sorted by unsigned request number. Network-interface ioctls @@ -192,10 +264,12 @@ pub const IOCTL_REQUEST_CONTRACTS: &[IoctlRequestContract] = &[ no_arg!(crate::dri::DRM_IOCTL_SET_MASTER), no_arg!(crate::dri::DRM_IOCTL_DROP_MASTER), pointer!(SIOCATMARK, Out, 4), + scalar_i32!(crate::input::EVIOCGRAB), pointer!(crate::oss::SNDCTL_DSP_SETBLKSIZE, In, 4), pointer!(crate::oss::SNDCTL_DSP_SETTRIGGER, In, 4), pointer!(TIOCSPTLCK, In, 4), pointer!(crate::dri::DRM_IOCTL_GEM_CLOSE, In, 8), + pointer!(crate::input::EVIOCGVERSION, Out, 4), pointer!(crate::oss::SOUND_PCM_READ_RATE, Out, 4), pointer!(crate::oss::SOUND_PCM_READ_BITS, Out, 4), pointer!(crate::oss::SOUND_PCM_READ_CHANNELS, Out, 4), @@ -205,6 +279,11 @@ pub const IOCTL_REQUEST_CONTRACTS: &[IoctlRequestContract] = &[ pointer!(crate::oss::SNDCTL_DSP_GETTRIGGER, Out, 4), pointer!(crate::oss::SNDCTL_DSP_GETODELAY, Out, 4), pointer!(TIOCGPTN, Out, 4), + pointer!( + crate::input::EVIOCGID, + Out, + core::mem::size_of::() as u32 + ), pointer!(crate::oss::SNDCTL_DSP_MAPINBUF, Out, 8), pointer!(crate::oss::SNDCTL_DSP_MAPOUTBUF, Out, 8), pointer!(crate::oss::SNDCTL_DSP_GETIPTR, Out, 12), @@ -239,11 +318,50 @@ pub const IOCTL_REQUEST_CONTRACTS: &[IoctlRequestContract] = &[ pointer!(crate::dri::DRM_IOCTL_MODE_ADDFB2, InOut, 104), ]; -pub fn request_contract(request: u32) -> Option<&'static IoctlRequestContract> { - IOCTL_REQUEST_CONTRACTS - .binary_search_by_key(&request, |entry| entry.request) - .ok() - .map(|index| &IOCTL_REQUEST_CONTRACTS[index]) +pub fn request_contract(request: u32) -> Option { + if let Ok(index) = + IOCTL_REQUEST_CONTRACTS.binary_search_by_key(&request, |entry| entry.request) + { + return Some(IOCTL_REQUEST_CONTRACTS[index]); + } + family_request_contract(request) +} + +/// Resolve a request that varies by caller-chosen length or by axis. +/// +/// Returns `None` for a member whose encoded size the family does not allow, +/// so a malformed request stays unknown rather than staging a wrong buffer. +pub fn family_request_contract(request: u32) -> Option { + let dir = (request >> 30) & 0x3; + let encoded_size = (request >> 16) & 0x3fff; + let magic = (request >> 8) & 0xff; + let nr = request & 0xff; + + let family = IOCTL_REQUEST_FAMILIES.iter().find(|family| { + family.dir == dir + && family.magic == magic + && family.nr_first <= nr + && nr <= family.nr_last + })?; + + let size = match family.size { + IoctlFamilySize::Fixed(fixed) if encoded_size == fixed => fixed, + IoctlFamilySize::Fixed(_) => return None, + IoctlFamilySize::CallerEncoded { max } + if encoded_size >= 1 && encoded_size <= max => + { + encoded_size + } + IoctlFamilySize::CallerEncoded { .. } => return None, + }; + + Some(IoctlRequestContract { + request, + arg_kind: IoctlArgKind::Pointer, + direction: family.direction, + wasm32_size: Some(size), + wasm64_size: Some(size), + }) } #[cfg(test)] @@ -274,4 +392,94 @@ mod tests { assert_eq!(query.size_for_pointer_width(4), Some(24)); assert_eq!(query.size_for_pointer_width(8), None); } + + /// Builds the same encoding the musl `_IOC` macros produce. + const fn evioc(dir: u32, nr: u32, size: u32) -> u32 { + (dir << 30) | (size << 16) | ((b'E' as u32) << 8) | nr + } + + #[test] + fn fixed_evdev_requests_resolve_through_the_sorted_table() { + let version = request_contract(crate::input::EVIOCGVERSION).unwrap(); + assert_eq!(version.arg_kind, IoctlArgKind::Pointer); + assert_eq!(version.size_for_pointer_width(4), Some(4)); + + let id = request_contract(crate::input::EVIOCGID).unwrap(); + assert_eq!(id.arg_kind, IoctlArgKind::Pointer); + assert_eq!(id.size_for_pointer_width(4), Some(8)); + + let grab = request_contract(crate::input::EVIOCGRAB).unwrap(); + assert_eq!(grab.arg_kind, IoctlArgKind::ScalarI32); + } + + #[test] + fn evioc_gabs_resolves_every_axis_at_the_absinfo_size() { + for axis in 0..64 { + let request = evioc(2, crate::input::EVIOCGABS_NR_BASE + axis, 24); + let contract = request_contract(request).unwrap(); + assert_eq!(contract.arg_kind, IoctlArgKind::Pointer); + assert_eq!(contract.size_for_pointer_width(4), Some(24)); + assert_eq!(contract.size_for_pointer_width(8), Some(24)); + } + } + + #[test] + fn evioc_gabs_rejects_a_size_other_than_absinfo() { + let request = evioc(2, crate::input::EVIOCGABS_NR_BASE, 16); + assert_eq!(request_contract(request), None); + } + + #[test] + fn caller_encoded_evdev_requests_carry_the_callers_length() { + for size in [1, 32, EVIOC_MAX_CALLER_LENGTH] { + let name = evioc(2, crate::input::EVIOCGNAME_NR, size); + assert_eq!( + request_contract(name).unwrap().size_for_pointer_width(4), + Some(size), + ); + + let bit = evioc(2, crate::input::EVIOCGBIT_NR_BASE + 1, size); + assert_eq!( + request_contract(bit).unwrap().size_for_pointer_width(4), + Some(size), + ); + } + } + + #[test] + fn caller_encoded_evdev_requests_reject_zero_and_oversized_lengths() { + for nr in [ + crate::input::EVIOCGNAME_NR, + crate::input::EVIOCGBIT_NR_BASE, + ] { + assert_eq!(request_contract(evioc(2, nr, 0)), None); + assert_eq!( + request_contract(evioc(2, nr, EVIOC_MAX_CALLER_LENGTH + 1)), + None, + ); + } + } + + #[test] + fn evdev_families_ignore_a_foreign_magic_or_write_direction() { + let foreign_magic = + (2 << 30) | (24 << 16) | ((b'D' as u32) << 8) | crate::input::EVIOCGABS_NR_BASE; + assert_eq!(request_contract(foreign_magic), None); + assert_eq!( + request_contract(evioc(1, crate::input::EVIOCGABS_NR_BASE, 24)), + None, + ); + } + + #[test] + fn family_ranges_do_not_overlap_the_sorted_table() { + for contract in IOCTL_REQUEST_CONTRACTS { + assert_eq!( + family_request_contract(contract.request), + None, + "request {:#x} resolves through both tables", + contract.request, + ); + } + } } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index d5e6d517ac..99581090a4 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -850,6 +850,7 @@ pub enum Errno { EIDRM = 43, ENODATA = 61, EOVERFLOW = 75, + EBADFD = 77, ENOTSOCK = 88, EDESTADDRREQ = 89, EMSGSIZE = 90, @@ -920,6 +921,7 @@ impl Errno { 43 => Some(Errno::EIDRM), 61 => Some(Errno::ENODATA), 75 => Some(Errno::EOVERFLOW), + 77 => Some(Errno::EBADFD), 88 => Some(Errno::ENOTSOCK), 89 => Some(Errno::EDESTADDRREQ), 90 => Some(Errno::EMSGSIZE), @@ -4985,6 +4987,332 @@ pub mod dri { } } +/// evdev — `/dev/input/event*` UAPI exposed to user programs. +/// +/// Mirror image of [`dri`] above: the kernel synthesises records and +/// user programs drain them through `read()` / `poll()`. The struct +/// layouts, ioctl numbers, and code points here are Linux-verbatim so +/// libinput / SDL2 / X11 evdev paths can be ported without an +/// abstraction layer. +/// +/// **Additive only.** Adding new `EV_*` / `KEY_*` / `EVIOC*` is +/// allowed without bumping [`ABI_VERSION`]; changing the layout of +/// [`input::WpkInputEvent`] or the value of any existing constant is +/// not. +pub mod input { + // --- Event types (struct input_event.type) --------------------------- + + /// `EV_SYN` = 0. End-of-logical-event sentinel; readers use this + /// to coalesce a (REL_X, REL_Y) pair into one cursor move. + pub const EV_SYN: u16 = 0x00; + /// `EV_KEY` = 1. Press / release / autorepeat. Value is + /// 0 = release, 1 = press, 2 = repeat. + pub const EV_KEY: u16 = 0x01; + /// `EV_REL` = 2. Relative axis (pointer dx/dy/wheel). + pub const EV_REL: u16 = 0x02; + /// `EV_ABS` = 3. Absolute axis (pointer position when not locked, + /// joystick, touch coords). + pub const EV_ABS: u16 = 0x03; + /// `EV_MSC` = 4. Misc events (scancode, timestamp). Not produced + /// in v1. + pub const EV_MSC: u16 = 0x04; + + // --- SYN codes (struct input_event.code when type == EV_SYN) --------- + + /// `SYN_REPORT` = 0. End-of-frame; readers should treat + /// everything since the previous `SYN_REPORT` as atomic. + pub const SYN_REPORT: u16 = 0x00; + /// `SYN_DROPPED` = 3. Posted when the ring overflowed and the + /// oldest record was dropped; userspace should resynchronise + /// (re-query EVIOCG* state). + pub const SYN_DROPPED: u16 = 0x03; + + // --- KEY_* codes (verbatim from linux/input-event-codes.h) ----------- + // + // Range 0..248 covers every code Chrome / Firefox / WebKit emit + // through `KeyboardEvent.code`; values >248 (KEY_BUTTONCONFIG, the + // KEY_VENDOR range, etc.) are not browser-reachable. + + pub const KEY_RESERVED: u16 = 0; + pub const KEY_ESC: u16 = 1; + pub const KEY_1: u16 = 2; + pub const KEY_2: u16 = 3; + pub const KEY_3: u16 = 4; + pub const KEY_4: u16 = 5; + pub const KEY_5: u16 = 6; + pub const KEY_6: u16 = 7; + pub const KEY_7: u16 = 8; + pub const KEY_8: u16 = 9; + pub const KEY_9: u16 = 10; + pub const KEY_0: u16 = 11; + pub const KEY_MINUS: u16 = 12; + pub const KEY_EQUAL: u16 = 13; + pub const KEY_BACKSPACE: u16 = 14; + pub const KEY_TAB: u16 = 15; + pub const KEY_Q: u16 = 16; + pub const KEY_W: u16 = 17; + pub const KEY_E: u16 = 18; + pub const KEY_R: u16 = 19; + pub const KEY_T: u16 = 20; + pub const KEY_Y: u16 = 21; + pub const KEY_U: u16 = 22; + pub const KEY_I: u16 = 23; + pub const KEY_O: u16 = 24; + pub const KEY_P: u16 = 25; + pub const KEY_LEFTBRACE: u16 = 26; + pub const KEY_RIGHTBRACE: u16 = 27; + pub const KEY_ENTER: u16 = 28; + pub const KEY_LEFTCTRL: u16 = 29; + pub const KEY_A: u16 = 30; + pub const KEY_S: u16 = 31; + pub const KEY_D: u16 = 32; + pub const KEY_F: u16 = 33; + pub const KEY_G: u16 = 34; + pub const KEY_H: u16 = 35; + pub const KEY_J: u16 = 36; + pub const KEY_K: u16 = 37; + pub const KEY_L: u16 = 38; + pub const KEY_SEMICOLON: u16 = 39; + pub const KEY_APOSTROPHE: u16 = 40; + pub const KEY_GRAVE: u16 = 41; + pub const KEY_LEFTSHIFT: u16 = 42; + pub const KEY_BACKSLASH: u16 = 43; + pub const KEY_Z: u16 = 44; + pub const KEY_X: u16 = 45; + pub const KEY_C: u16 = 46; + pub const KEY_V: u16 = 47; + pub const KEY_B: u16 = 48; + pub const KEY_N: u16 = 49; + pub const KEY_M: u16 = 50; + pub const KEY_COMMA: u16 = 51; + pub const KEY_DOT: u16 = 52; + pub const KEY_SLASH: u16 = 53; + pub const KEY_RIGHTSHIFT: u16 = 54; + pub const KEY_KPASTERISK: u16 = 55; + pub const KEY_LEFTALT: u16 = 56; + pub const KEY_SPACE: u16 = 57; + pub const KEY_CAPSLOCK: u16 = 58; + pub const KEY_F1: u16 = 59; + pub const KEY_F2: u16 = 60; + pub const KEY_F3: u16 = 61; + pub const KEY_F4: u16 = 62; + pub const KEY_F5: u16 = 63; + pub const KEY_F6: u16 = 64; + pub const KEY_F7: u16 = 65; + pub const KEY_F8: u16 = 66; + pub const KEY_F9: u16 = 67; + pub const KEY_F10: u16 = 68; + pub const KEY_NUMLOCK: u16 = 69; + pub const KEY_SCROLLLOCK: u16 = 70; + pub const KEY_KP7: u16 = 71; + pub const KEY_KP8: u16 = 72; + pub const KEY_KP9: u16 = 73; + pub const KEY_KPMINUS: u16 = 74; + pub const KEY_KP4: u16 = 75; + pub const KEY_KP5: u16 = 76; + pub const KEY_KP6: u16 = 77; + pub const KEY_KPPLUS: u16 = 78; + pub const KEY_KP1: u16 = 79; + pub const KEY_KP2: u16 = 80; + pub const KEY_KP3: u16 = 81; + pub const KEY_KP0: u16 = 82; + pub const KEY_KPDOT: u16 = 83; + pub const KEY_ZENKAKUHANKAKU: u16 = 85; + pub const KEY_102ND: u16 = 86; + pub const KEY_F11: u16 = 87; + pub const KEY_F12: u16 = 88; + pub const KEY_RO: u16 = 89; + pub const KEY_KATAKANA: u16 = 90; + pub const KEY_HIRAGANA: u16 = 91; + pub const KEY_HENKAN: u16 = 92; + pub const KEY_KATAKANAHIRAGANA: u16 = 93; + pub const KEY_MUHENKAN: u16 = 94; + pub const KEY_KPJPCOMMA: u16 = 95; + pub const KEY_KPENTER: u16 = 96; + pub const KEY_RIGHTCTRL: u16 = 97; + pub const KEY_KPSLASH: u16 = 98; + pub const KEY_SYSRQ: u16 = 99; + pub const KEY_RIGHTALT: u16 = 100; + pub const KEY_LINEFEED: u16 = 101; + pub const KEY_HOME: u16 = 102; + pub const KEY_UP: u16 = 103; + pub const KEY_PAGEUP: u16 = 104; + pub const KEY_LEFT: u16 = 105; + pub const KEY_RIGHT: u16 = 106; + pub const KEY_END: u16 = 107; + pub const KEY_DOWN: u16 = 108; + pub const KEY_PAGEDOWN: u16 = 109; + pub const KEY_INSERT: u16 = 110; + pub const KEY_DELETE: u16 = 111; + pub const KEY_MACRO: u16 = 112; + pub const KEY_MUTE: u16 = 113; + pub const KEY_VOLUMEDOWN: u16 = 114; + pub const KEY_VOLUMEUP: u16 = 115; + pub const KEY_POWER: u16 = 116; + pub const KEY_KPEQUAL: u16 = 117; + pub const KEY_KPPLUSMINUS: u16 = 118; + pub const KEY_PAUSE: u16 = 119; + pub const KEY_SCALE: u16 = 120; + pub const KEY_KPCOMMA: u16 = 121; + pub const KEY_HANGEUL: u16 = 122; + pub const KEY_HANJA: u16 = 123; + pub const KEY_YEN: u16 = 124; + pub const KEY_LEFTMETA: u16 = 125; + pub const KEY_RIGHTMETA: u16 = 126; + pub const KEY_COMPOSE: u16 = 127; + pub const KEY_STOP: u16 = 128; + pub const KEY_AGAIN: u16 = 129; + pub const KEY_PROPS: u16 = 130; + pub const KEY_UNDO: u16 = 131; + pub const KEY_FRONT: u16 = 132; + pub const KEY_COPY: u16 = 133; + pub const KEY_OPEN: u16 = 134; + pub const KEY_PASTE: u16 = 135; + pub const KEY_FIND: u16 = 136; + pub const KEY_CUT: u16 = 137; + pub const KEY_HELP: u16 = 138; + pub const KEY_MENU: u16 = 139; + pub const KEY_CALC: u16 = 140; + pub const KEY_SLEEP: u16 = 142; + pub const KEY_WAKEUP: u16 = 143; + pub const KEY_PLAYPAUSE: u16 = 164; + pub const KEY_PREVIOUSSONG: u16 = 165; + pub const KEY_STOPCD: u16 = 166; + pub const KEY_NEXTSONG: u16 = 163; + pub const KEY_EJECTCD: u16 = 161; + pub const KEY_REFRESH: u16 = 173; + pub const KEY_F13: u16 = 183; + pub const KEY_F14: u16 = 184; + pub const KEY_F15: u16 = 185; + pub const KEY_F16: u16 = 186; + pub const KEY_F17: u16 = 187; + pub const KEY_F18: u16 = 188; + pub const KEY_F19: u16 = 189; + pub const KEY_F20: u16 = 190; + pub const KEY_F21: u16 = 191; + pub const KEY_F22: u16 = 192; + pub const KEY_F23: u16 = 193; + pub const KEY_F24: u16 = 194; + pub const KEY_PLAYCD: u16 = 200; + pub const KEY_PAUSECD: u16 = 201; + pub const KEY_BRIGHTNESSDOWN: u16 = 224; + pub const KEY_BRIGHTNESSUP: u16 = 225; + pub const KEY_MICMUTE: u16 = 248; + + // --- BTN_* codes (button class; reuse the EV_KEY event type) --------- + + pub const BTN_LEFT: u16 = 0x110; + pub const BTN_RIGHT: u16 = 0x111; + pub const BTN_MIDDLE: u16 = 0x112; + pub const BTN_SIDE: u16 = 0x113; + pub const BTN_EXTRA: u16 = 0x114; + + // --- REL_* codes (relative axes; EV_REL records carry these) --------- + + pub const REL_X: u16 = 0x00; + pub const REL_Y: u16 = 0x01; + pub const REL_HWHEEL: u16 = 0x06; + pub const REL_WHEEL: u16 = 0x08; + + // --- ABS_* codes (absolute axes; EV_ABS records carry these) --------- + + pub const ABS_X: u16 = 0x00; + pub const ABS_Y: u16 = 0x01; + + // --- BUS_* constants (subset) ---------------------------------------- + + /// `BUS_VIRTUAL` = 0x06 — closest match for a kernel-synthesised + /// device (Linux uses this for `uinput`-backed devices). + pub const BUS_VIRTUAL: u16 = 0x06; + + // --- ioctl numbers ('E' magic, Linux UAPI verbatim) ------------------ + // + // Encoding: `(dir << 30) | (size << 16) | (magic << 8) | nr`. + // `_IOR` = dir 2 (kernel writes back to userland buffer), + // `_IOW` = dir 1 (kernel reads from userland buffer). The + // `evioc_numbers_match_linux_uapi` test below re-derives each one + // through `ioc(...)` so a copy-paste typo cannot survive. + + /// `_IOR('E', 0x01, int)` = `0x8004_4501`. + pub const EVIOCGVERSION: u32 = 0x8004_4501; + + /// `_IOR('E', 0x02, WpkInputId)` = `0x8008_4502`. + pub const EVIOCGID: u32 = 0x8008_4502; + + /// `_IOC(_IOC_READ, 'E', 0x06, len)` — `EVIOCGNAME(len)` in C. + /// `len` is caller-supplied; A3 matches on `(dir, magic, nr)` + /// and recomputes the buffer size from the encoded `size` field + /// at dispatch time (1 ≤ size ≤ 256). + pub const EVIOCGNAME_NR: u32 = 0x06; + + /// `EVIOCGBIT(ev_type, len)` — same variable-length shape as + /// `EVIOCGNAME`. `nr = 0x20 + ev_type`. + pub const EVIOCGBIT_NR_BASE: u32 = 0x20; + + /// `EVIOCGABS(axis)` — `_IOR('E', 0x40 + axis, WpkInputAbsinfo)`. + /// `axis` is a small integer (`ABS_X = 0`, `ABS_Y = 1`, …). + pub const EVIOCGABS_NR_BASE: u32 = 0x40; + + /// `_IOW('E', 0x90, int)` = `0x4004_4590`. + pub const EVIOCGRAB: u32 = 0x4004_4590; + + // --- marshalled structs ---------------------------------------------- + + /// `struct input_event` on wasm32-musl (`time_t = int64_t`, + /// `suseconds_t = int32_t`, `__u16` + `__u16` + `__s32`). + /// Total = 24 bytes. + /// + /// The explicit `_pad: i32` at byte 12 is **load-bearing**. + /// `repr(C)` would otherwise place `ev_type` at offset 12 (no + /// interior padding between the `i32 tv_usec` and the `u16 + /// ev_type`), but C's `struct timeval` substruct is itself 16 + /// bytes on wasm32-musl: the `int64_t tv_sec` forces 8-byte + /// alignment of the substruct, and the trailing `int32_t + /// tv_usec` is padded to 16 to satisfy that alignment. So the + /// C reader expects `ev_type` at offset 16 while the + /// pad-less Rust writer would put it at offset 12 — silent + /// corruption on every record. The `input_event_field_offsets` + /// test below gates the layout; if `ev_type` ever drifts back + /// to offset 12, restore `_pad`. + #[repr(C)] + #[derive(Clone, Copy, Default)] + pub struct WpkInputEvent { + pub tv_sec: i64, // 0 CLOCK_MONOTONIC seconds since kernel boot + pub tv_usec: i32, // 8 microseconds; matches musl suseconds_t + pub _pad: i32, // 12 pad so the trailing union 8-aligns with C + pub ev_type: u16, // 16 EV_KEY / EV_REL / EV_ABS / EV_SYN / EV_MSC + pub code: u16, // 18 KEY_* / BTN_* / REL_* / ABS_* / SYN_* + pub value: i32, // 20 press/release/repeat; delta; absolute pos + // total: 24 + } + + /// `struct input_id` — 8 bytes (4 × u16). Returned by `EVIOCGID`. + #[repr(C)] + #[derive(Clone, Copy, Default)] + pub struct WpkInputId { + pub bustype: u16, // 0 BUS_VIRTUAL = 0x06 + pub vendor: u16, // 2 + pub product: u16, // 4 0x0001 = kbd, 0x0002 = ptr + pub version: u16, // 6 + // total: 8 + } + + /// `struct input_absinfo` — 24 bytes (6 × i32). Returned by + /// `EVIOCGABS(axis)`. Used for `ABS_X` / `ABS_Y` on the pointer + /// device when pointer lock is not active. + #[repr(C)] + #[derive(Clone, Copy, Default)] + pub struct WpkInputAbsinfo { + pub value: i32, // 0 current value + pub minimum: i32, // 4 + pub maximum: i32, // 8 canvas width-1 / height-1 + pub fuzz: i32, // 12 + pub flat: i32, // 16 + pub resolution: i32,// 20 1 unit per pixel + // total: 24 + } +} + #[cfg(test)] mod dri_tests { use super::dri::*; @@ -5323,3 +5651,75 @@ mod gl_tests { } } } + +#[cfg(test)] +mod input_tests { + use super::input::*; + use core::mem::size_of; + + // Linux's `_IOC` packs (dir, size, magic, nr) into a u32. + // Mirrors include/uapi/asm-generic/ioctl.h. `IOC_READ = 2` + // (`_IOR`); `IOC_WRITE = 1` (`_IOW`). + const fn ioc(dir: u32, magic: u32, nr: u32, size: u32) -> u32 { + (dir << 30) | (size << 16) | (magic << 8) | nr + } + const IOC_READ: u32 = 2; + const IOC_WRITE: u32 = 1; + + #[test] + fn input_struct_sizes_match_wasm32_repr_c() { + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 24); + } + + #[test] + fn input_event_field_offsets() { + // The 24-byte layout is load-bearing — every reader walks + // the ring 24 bytes at a time. Lock the offsets explicitly. + let e = WpkInputEvent::default(); + let base = (&e as *const _) as usize; + assert_eq!((&e.tv_sec as *const _ as usize) - base, 0); + assert_eq!((&e.tv_usec as *const _ as usize) - base, 8); + assert_eq!((&e.ev_type as *const _ as usize) - base, 16); + assert_eq!((&e.code as *const _ as usize) - base, 18); + assert_eq!((&e.value as *const _ as usize) - base, 20); + } + + #[test] + fn evioc_numbers_match_linux_uapi() { + assert_eq!( + EVIOCGVERSION, + ioc(IOC_READ, 'E' as u32, 0x01, 4) + ); + assert_eq!( + EVIOCGID, + ioc(IOC_READ, 'E' as u32, 0x02, size_of::() as u32) + ); + assert_eq!( + EVIOCGRAB, + ioc(IOC_WRITE, 'E' as u32, 0x90, 4) + ); + // EVIOCGABS(ABS_X) — exercises both the variable nr base + // and the absinfo struct size. + assert_eq!( + ioc( + IOC_READ, + 'E' as u32, + EVIOCGABS_NR_BASE + ABS_X as u32, + size_of::() as u32 + ), + 0x8018_4540 + ); + } + + #[test] + fn evioc_nr_bases_match_linux_uapi() { + // Spot-check the variable-length / per-axis bases used by + // A3's dispatch; the precise number is only known once the + // size field is filled in at ioctl time. + assert_eq!(EVIOCGNAME_NR, 0x06); + assert_eq!(EVIOCGBIT_NR_BASE, 0x20); + assert_eq!(EVIOCGABS_NR_BASE, 0x40); + } +} diff --git a/docs/browser-support.md b/docs/browser-support.md index 46d736b7bf..7e41d29f48 100644 --- a/docs/browser-support.md +++ b/docs/browser-support.md @@ -318,6 +318,8 @@ Located in `apps/browser-demos/pages/`: | benchmark | (per-suite) | legacy spawn | Micro-benchmarks + WordPress + Erlang ring | | network | dash + GNU Netcat + curl | `kernel.boot` x 3 | Boots multiple local Kandelo machines and verifies UDP datagrams, TCP streams, and HTTP over virtual TCP | | doom | fbDOOM | legacy spawn | `/dev/fb0` framebuffer + canvas renderer + keyboard via stdin + mouse via `/dev/input/mice` (pointer-locked) + SFX **and** OPL2-synthesized music via `/dev/dsp` → AudioContext. The shareware `doom1.wad` is **fetched at page load** from a commit-pinned CDN URL (SHA-256 verified, Cache API cached); no IWAD ships in the package archive. | +| evdev | evdev_demo | dinit | Reads `/dev/input/event{0,1}` and prints each record. A `BrowserInputSource` translates DOM key and pointer events into `EV_KEY`/`EV_REL`/`EV_ABS` and pushes them through `kernel_input_event`. The binary comes from the `evdev-demo` package and is baked into the image before boot; the input source is attached first, because the binary polls as soon as it runs. | +| espeak | espeak-ng | dinit | Speech synthesis through upstream pcaudiolib's OSS backend, so playback rides the same `/dev/dsp` path as the doom demo. The binary and the voice data both come from the `espeak-ng` package closure — the data as the `espeak-ng-data.zip` runtime file, unpacked into `/usr/share/espeak-ng-data` while the image is composed, because libespeak-ng's `PATH_ESPEAK_DATA` is fixed at build time. | The "Boot pattern" column reflects how the demo enters the kernel: - **`kernel.boot`** — `kernelOwnedFs: true`, exec the language interpreter as the first user process. diff --git a/docs/posix-status.md b/docs/posix-status.md index 65f00d5431..1a3ab10ffe 100644 --- a/docs/posix-status.md +++ b/docs/posix-status.md @@ -403,7 +403,8 @@ proves and reserves only the 128-byte prefix the kernel can write. | `/dev/ptmx` | Full | PTY master multiplexer. `open()` allocates a new PTY pair, returns master fd. | | `/dev/pts/*` | Full | PTY slave devices. Allocation captures the creator's effective UID and, because no separate tty group is configured, effective GID, with mode `0620`. `stat()`, `lstat()`, `fstatat()`, `statx()`, and descriptor stat share that persistent record; authorized chmod/chown operations update it, and open checks use the caller's current effective credentials and complete supplementary groups. Metadata survives slave close/reopen and is discarded with the pair. `/dev/ptmx` remains a distinct root-owned clone node. Also supports `posix_openpt()` + `grantpt()` + `unlockpt()` + `ptsname()`, full line discipline, canonical/raw mode, OPOST/ONLCR, and 16 terminal ioctls. | | `/dev/fb0` | Full | Linux fbdev framebuffer. Single-open (`EBUSY` for second opener). 640×400 BGRA32 packed-pixel. ioctls: `FBIOGET_VSCREENINFO`, `FBIOGET_FSCREENINFO`, `FBIOPAN_DISPLAY` (no-op success), `FBIOPUT_VSCREENINFO` (validates geometry). `mmap` returns a region in process memory and notifies the host (`bind_framebuffer` callback) so the browser canvas can mirror pixels. `munmap`/`exit`/`exec` discard the image mapping; a surviving fd retains device ownership across exec. Ownership is released after both the final fd and any live mapping are gone, since a mapping remains valid after `close()`. Linux-VT keyboard ioctls (`KDGKBTYPE`/`KDGKBMODE`/`KDSKBMODE`) are accepted on the process's terminal fd so fbDOOM-style software works unmodified; `/dev/fb0` itself is not a terminal. | -| `/dev/input/mice` | Full | Linux `mousedev` PS/2 mouse stream. Single-open (`EBUSY` for second pid). 3-byte packets: byte0 button bits + sign/overflow flags, bytes 1..2 signed dx/dy with positive-up dy. Host pushes events via `kernel_inject_mouse_event(dx, dy, buttons)`; the kernel buffers up to 4096 packets (whole-packet drop on overflow). `read()` drains queued bytes; returns `EAGAIN` when empty. `poll()` reports `POLLIN` only when bytes are queued. Ownership and queued packets survive exec with a non-CLOEXEC fd; last close or exit releases and clears them. No IMPS/2 wheel protocol, no `evdev`/`/dev/input/eventN`. | +| `/dev/input/mice` | Full | Linux `mousedev` PS/2 mouse stream. Single-open (`EBUSY` for second pid). 3-byte packets: byte0 button bits + sign/overflow flags, bytes 1..2 signed dx/dy with positive-up dy. Host pushes events via `kernel_inject_mouse_event(dx, dy, buttons)`; the kernel buffers up to 4096 packets (whole-packet drop on overflow). `read()` drains queued bytes; returns `EAGAIN` when empty. `poll()` reports `POLLIN` only when bytes are queued. Ownership and queued packets survive exec with a non-CLOEXEC fd; last close or exit releases and clears them. No IMPS/2 wheel protocol; `evdev` lives on `/dev/input/event{0,1}` below. | +| `/dev/input/event0`, `/dev/input/event1` | Partial | Linux `evdev` character devices: `event0` is the keyboard, `event1` the pointer. `read()` drains whole 24-byte `struct input_event` records and returns `EAGAIN` when the ring is empty; `poll()` reports `POLLIN` only while records are queued. Each OFD owns its own 1024-record ring, so several readers see the stream independently. On overflow the ring latches a drop and the next `read()` returns a `SYN_DROPPED` record so the client can resynchronise. The host pushes records with `kernel_input_event(device, type, code, value)` and publishes the canvas size with `kernel_set_input_canvas_dims(width, height)`, which is what `EVIOCGABS` reports as the `ABS_X`/`ABS_Y` maxima. ioctls: `EVIOCGVERSION` (reports 1.0.1), `EVIOCGID` (`BUS_VIRTUAL`, vendor `0x1209`), `EVIOCGNAME` (`wpk virtual keyboard` / `wpk virtual pointer`, truncated to the caller's length), `EVIOCGBIT` for `EV_*` capability bitmaps, `EVIOCGABS` on `ABS_X`/`ABS_Y` (pointer only; other axes and the keyboard return `ENOTTY`), and `EVIOCGRAB` (records the grab on the OFD without changing routing, since nothing competes for the stream). Every other `E`-magic request returns `ENOTTY`. Ring state is per OFD: released on last close, carried across `fork()` and `exec()` with the descriptor. No force feedback, no `EVIOCSABS`/`EVIOCGKEY`/`EVIOCGLED`, no hotplug, and no devices beyond these two. | | `/dev/dsp` | Partial (playback only) | Source-compatible OSS PCM playback over the implementation-neutral Kandelo PCM core. U8/S16_LE/S16_BE, mono/stereo, 8–192 kHz; bounded fragment queue with blocking/nonblocking backpressure and audio-clock drain. Exclusive ownership is per OFD, not PID. See the matrix below. Capture, duplex, mmap, mixer controls, and multi-client mixing are unsupported. | | `/dev/shm/*` | Partial | POSIX shm objects are regular files used by `shm_open()`. Stable-identity backends support host-coordinated `MAP_SHARED` across processes at syscall boundaries; this is not immediate shared linear memory and does not make process-shared futexes work. | diff --git a/host/src/browser-kernel-host.ts b/host/src/browser-kernel-host.ts index f5618f7e9b..7e86794e2e 100644 --- a/host/src/browser-kernel-host.ts +++ b/host/src/browser-kernel-host.ts @@ -24,6 +24,7 @@ import { type BrowserCorsProxyConfig, validateBrowserCorsProxyConfig, } from "./networking/browser-cors-proxy"; +import type { InputSource } from "./input/input-source"; export type { HttpRequest, HttpResponse }; import workerEntryUrl from "./worker-entry-browser.ts?worker&url"; @@ -1059,6 +1060,55 @@ export class BrowserKernel { this.sendToKernel({ type: "mouse_inject", dx, dy, buttons }); } + /** + * Push one evdev record into the kernel's `/dev/input/event{0,1}` + * ring. `device` is 0 for the keyboard, 1 for the pointer; the + * other three fields mirror Linux `struct input_event`. Apps + * normally route through `attachInputSource` and never call this + * directly — exposed for tests + niche injection paths. + */ + injectInputEvent( + device: 0 | 1, + ev_type: number, + code: number, + value: number, + ): void { + this.sendToKernel({ + type: "input_event_inject", + device, + ev_type, + code, + value, + }); + } + + /** + * Tell the kernel the current host canvas dimensions so EVIOCGABS + * on `/dev/input/event1` reports `ABS_X.maximum = width - 1` and + * `ABS_Y.maximum = height - 1`. Call once at boot when the canvas + * is attached and again on any resize. + */ + setInputCanvasDims(width: number, height: number): void { + this.sendToKernel({ type: "set_input_canvas_dims", width, height }); + } + + /** + * Wire an `InputSource` into the kernel: sets canvas dims, then + * starts the source with a dispatch callback that funnels each + * emitted record through `injectInputEvent`. Mirrors + * `NodeKernelHost.attachInputSource` — dual-host parity per + * CLAUDE.md §"Two hosts". + */ + attachInputSource( + source: InputSource, + dims: { width: number; height: number }, + ): void { + this.setInputCanvasDims(dims.width, dims.height); + source.start((ev) => + this.injectInputEvent(ev.device, ev.ev_type, ev.code, ev.value), + ); + } + /** * Hand an `OffscreenCanvas` to the kernel worker as the scanout * target for KMS CRTC `crtcId`. The worker's vblank pump blits the diff --git a/host/src/browser-kernel-protocol.ts b/host/src/browser-kernel-protocol.ts index d122b04aab..0ac99e8d46 100644 --- a/host/src/browser-kernel-protocol.ts +++ b/host/src/browser-kernel-protocol.ts @@ -310,6 +310,35 @@ export interface MouseInjectMessage { buttons: number; } +/** + * Main-thread → kernel-worker evdev injection. The main thread's + * `BrowserInputSource` translates DOM events to evdev records and + * forwards them here; the worker calls + * `CentralizedKernelWorker.injectInputEvent` which routes the record + * through the kernel's fan-out (`kernel_input_event` → `push_event`) + * to `/dev/input/event{0,1}` and wakes any blocked reader. + */ +export interface InputEventInjectMessage { + type: "input_event_inject"; + device: 0 | 1; + ev_type: number; + code: number; + value: number; +} + +/** + * Main-thread → kernel-worker canvas-dims update. Tells the kernel + * the current host canvas dimensions so EVIOCGABS on + * `/dev/input/event1` reports the right `ABS_X.maximum` / + * `ABS_Y.maximum`. Sent at boot once the canvas exists; resend on + * canvas resize. + */ +export interface SetInputCanvasDimsMessage { + type: "set_input_canvas_dims"; + width: number; + height: number; +} + /** * Main-thread → kernel-worker audio drain request. The main thread's * AudioContext scheduler ticks every ~50 ms, asks the kernel ring for @@ -461,6 +490,8 @@ export type MainToKernelMessage = | GetKernelMemoryPagesRequestMessage | GetSpawnScratchCapacityRequestMessage | MouseInjectMessage + | InputEventInjectMessage + | SetInputCanvasDimsMessage | AudioDrainMessage | EnumProcsRequestMessage | ReadProcMapsRequestMessage diff --git a/host/src/browser-kernel-worker-entry.ts b/host/src/browser-kernel-worker-entry.ts index 7298d4dc67..8bb2d8dfb0 100644 --- a/host/src/browser-kernel-worker-entry.ts +++ b/host/src/browser-kernel-worker-entry.ts @@ -4409,6 +4409,12 @@ sw.onmessage = (e: MessageEvent) => { case "fb_release_generation_ack": acknowledgeMainFramebufferRelease(msg.requestId); break; + case "input_event_inject": + kernelWorker.injectInputEvent(msg.device, msg.ev_type, msg.code, msg.value); + break; + case "set_input_canvas_dims": + kernelWorker.setInputCanvasDims(msg.width, msg.height); + break; default: { // Every typed MainToKernelMessage must have a case above. Browser // tooling also sends a few deliberately out-of-band control messages, diff --git a/host/src/generated/abi.ts b/host/src/generated/abi.ts index 79dcfb8ad0..c5e513bd55 100644 --- a/host/src/generated/abi.ts +++ b/host/src/generated/abi.ts @@ -1590,10 +1590,12 @@ export const IOCTL_REQUESTS: Record = { 25630: { argKind: "none", direction: "none", wasm32Size: 0, wasm64Size: 0 }, 25631: { argKind: "none", direction: "none", wasm32Size: 0, wasm64Size: 0 }, 35077: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, + 1074021776: { argKind: "scalar-i32", direction: "none", wasm32Size: 0, wasm64Size: 0 }, 1074024452: { argKind: "pointer", direction: "in", wasm32Size: 4, wasm64Size: 4 }, 1074024464: { argKind: "pointer", direction: "in", wasm32Size: 4, wasm64Size: 4 }, 1074025521: { argKind: "pointer", direction: "in", wasm32Size: 4, wasm64Size: 4 }, 1074291721: { argKind: "pointer", direction: "in", wasm32Size: 8, wasm64Size: 8 }, + 2147763457: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, 2147766274: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, 2147766277: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, 2147766278: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, @@ -1603,6 +1605,7 @@ export const IOCTL_REQUESTS: Record = { 2147766288: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, 2147766295: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, 2147767344: { argKind: "pointer", direction: "out", wasm32Size: 4, wasm64Size: 4 }, + 2148025602: { argKind: "pointer", direction: "out", wasm32Size: 8, wasm64Size: 8 }, 2148028435: { argKind: "pointer", direction: "out", wasm32Size: 8, wasm64Size: 8 }, 2148028436: { argKind: "pointer", direction: "out", wasm32Size: 8, wasm64Size: 8 }, 2148290577: { argKind: "pointer", direction: "out", wasm32Size: 12, wasm64Size: 12 }, @@ -1636,6 +1639,22 @@ export const IOCTL_REQUESTS: Record = { 3228067000: { argKind: "pointer", direction: "inout", wasm32Size: 104, wasm64Size: 104 }, }; +export interface IoctlRequestFamily { + dir: number; + magic: number; + nrFirst: number; + nrLast: number; + direction: IoctlDirection; + fixedSize: number | null; + maxCallerSize: number | null; +} + +export const IOCTL_REQUEST_FAMILIES: IoctlRequestFamily[] = [ + { dir: 2, magic: 69, nrFirst: 6, nrLast: 6, direction: "out", fixedSize: null, maxCallerSize: 256 }, + { dir: 2, magic: 69, nrFirst: 32, nrLast: 63, direction: "out", fixedSize: null, maxCallerSize: 256 }, + { dir: 2, magic: 69, nrFirst: 64, nrLast: 127, direction: "out", fixedSize: 24, maxCallerSize: null }, +]; + export const SYSCALL_ARGS: Record = { 1: [ { argIndex: 0, direction: "in", size: { type: "cstring", maxBytes: 4096, tooLongErrno: 36 }, required: true }, diff --git a/host/src/input/browser-input-source.ts b/host/src/input/browser-input-source.ts new file mode 100644 index 0000000000..fd7e9e7947 --- /dev/null +++ b/host/src/input/browser-input-source.ts @@ -0,0 +1,156 @@ +/** + * `BrowserInputSource` — captures DOM keyboard/pointer/wheel events, + * translates them to Linux evdev records (`KEY_*`, `BTN_*`, `REL_*`, + * `ABS_*`) and closes each logical input with a `SYN_REPORT`. Wired + * into `kernel.exports.kernel_input_event` by the browser host's worker + * entry at boot (B4). + * + * Coordinate convention: + * - Pointer-lock active → REL_X / REL_Y deltas (from movementX/Y). + * - Pointer-lock inactive → ABS_X / ABS_Y absolute (from offsetX/Y). + * On a lock-state transition we emit a bare SYN_REPORT so libinput / + * SDL2 see a re-sync point and don't carry forward a stale axis + * value. + */ +import type { InputSource, InputEvent } from "./input-source.js"; +import { codeToKey } from "./key-code-table.js"; + +const EV_SYN = 0x00, + EV_KEY = 0x01, + EV_REL = 0x02, + EV_ABS = 0x03; +const SYN_REPORT = 0x00; +const REL_X = 0x00, + REL_Y = 0x01, + REL_WHEEL = 0x08, + REL_HWHEEL = 0x06; +const ABS_X = 0x00, + ABS_Y = 0x01; +const BTN_LEFT = 0x110, + BTN_RIGHT = 0x111, + BTN_MIDDLE = 0x112; + +export class BrowserInputSource implements InputSource { + private dispatch: ((ev: InputEvent) => void) | null = null; + private bindings: Array<[EventTarget, string, EventListener]> = []; + + constructor(private target: EventTarget = window) {} + + start(dispatch: (ev: InputEvent) => void): void { + this.dispatch = dispatch; + this.bind("keydown", this.onKeyDown); + this.bind("keyup", this.onKeyUp); + this.bind("pointermove", this.onPointerMove); + this.bind("pointerdown", this.onPointerDown); + this.bind("pointerup", this.onPointerUp); + this.bind("wheel", this.onWheel); + // `pointerlockchange` only fires on document, never on window — so + // it can't go through this.bind which is parametric over `target`. + // Tracked in `bindings` for symmetric removal in stop(). + const lockHandler = this.onPointerLockChange.bind(this) as EventListener; + this.bindings.push([document, "pointerlockchange", lockHandler]); + document.addEventListener("pointerlockchange", lockHandler); + } + + stop(): void { + for (const [t, n, l] of this.bindings) t.removeEventListener(n, l); + this.bindings = []; + this.dispatch = null; + } + + private bind(name: string, handler: (e: any) => void) { + const wrapped = handler.bind(this); + this.target.addEventListener(name, wrapped as EventListener); + this.bindings.push([this.target, name, wrapped as EventListener]); + } + + private emit( + device: 0 | 1, + ev_type: number, + code: number, + value: number, + ): void { + this.dispatch!({ device, ev_type, code, value }); + } + + private frame(device: 0 | 1): void { + this.emit(device, EV_SYN, SYN_REPORT, 0); + } + + private onPointerLockChange(): void { + this.frame(1); + } + + private onKeyDown(e: KeyboardEvent): void { + const key = codeToKey(e.code); + if (key === null) return; + e.preventDefault(); + this.emit(0, EV_KEY, key, e.repeat ? 2 : 1); + this.frame(0); + } + + private onKeyUp(e: KeyboardEvent): void { + const key = codeToKey(e.code); + if (key === null) return; + e.preventDefault(); + this.emit(0, EV_KEY, key, 0); + this.frame(0); + } + + private onPointerMove(e: PointerEvent): void { + if (document.pointerLockElement) { + if (e.movementX !== 0) this.emit(1, EV_REL, REL_X, e.movementX); + if (e.movementY !== 0) this.emit(1, EV_REL, REL_Y, e.movementY); + } else { + this.emit(1, EV_ABS, ABS_X, Math.round(e.offsetX)); + this.emit(1, EV_ABS, ABS_Y, Math.round(e.offsetY)); + } + this.frame(1); + } + + private onPointerDown(e: PointerEvent): void { + const btn = pointerButton(e); + if (btn === null) return; + this.emit(1, EV_KEY, btn, 1); + this.frame(1); + } + + private onPointerUp(e: PointerEvent): void { + const btn = pointerButton(e); + if (btn === null) return; + this.emit(1, EV_KEY, btn, 0); + this.frame(1); + } + + private onWheel(e: WheelEvent): void { + e.preventDefault(); + // Browser deltaMode quanta: 0 = PIXEL (Safari ±1–10, Chromium + // ±100/±120 per notch), 1 = LINE (Firefox, ±3 per notch). Divide + // by the mode-specific scale, then clamp small-but-nonzero deltas + // to ±1 so a continuous-trackpad scroll still emits at least one + // tick (otherwise Math.trunc(0.3 / 120) = 0 and the entire scroll + // event disappears). + const scaleY = e.deltaMode === 1 ? 1 : 120; + const scaleX = e.deltaMode === 1 ? 1 : 120; + let ticks_y = Math.trunc(e.deltaY / -scaleY); + let ticks_x = Math.trunc(e.deltaX / scaleX); + if (ticks_y === 0 && e.deltaY !== 0) ticks_y = e.deltaY < 0 ? 1 : -1; + if (ticks_x === 0 && e.deltaX !== 0) ticks_x = e.deltaX > 0 ? 1 : -1; + if (ticks_y !== 0) this.emit(1, EV_REL, REL_WHEEL, ticks_y); + if (ticks_x !== 0) this.emit(1, EV_REL, REL_HWHEEL, ticks_x); + if (ticks_y !== 0 || ticks_x !== 0) this.frame(1); + } +} + +function pointerButton(e: PointerEvent): number | null { + switch (e.button) { + case 0: + return BTN_LEFT; + case 1: + return BTN_MIDDLE; + case 2: + return BTN_RIGHT; + default: + return null; + } +} diff --git a/host/src/input/input-source.ts b/host/src/input/input-source.ts new file mode 100644 index 0000000000..971c7cff79 --- /dev/null +++ b/host/src/input/input-source.ts @@ -0,0 +1,33 @@ +/** + * `InputSource` — host-side abstraction over an evdev-shaped event + * producer. One implementation per host: `BrowserInputSource` captures + * DOM events (keyboard + pointer + wheel) and translates them to Linux + * evdev codes; `NodeInputSource` is a null-source for headless test + * runs. The host wires `dispatch` to `kernel.exports.kernel_input_event` + * at boot, after `kernel.exports.kernel_set_input_canvas_dims`. + */ + +/** Records a single evdev-shaped event ready for kernel dispatch. + * + * `device` selects the virtual device: `0` is the keyboard + * (`/dev/input/event0`), `1` is the pointer (`/dev/input/event1`). + * `ev_type`, `code`, `value` mirror the Linux `struct input_event` + * tail — see `linux/input-event-codes.h` for the constant space. + */ +export interface InputEvent { + device: 0 | 1; + ev_type: number; + code: number; + value: number; +} + +export interface InputSource { + /** Begin capturing input. `dispatch` is called once per evdev record. + * Convention: the source emits the type-specific record (EV_KEY, + * EV_REL, EV_ABS, …) and then an `EV_SYN(SYN_REPORT, 0)` to close + * the logical frame — same shape Linux evdev produces. */ + start(dispatch: (ev: InputEvent) => void): void; + + /** Stop capturing; remove DOM listeners or clear timers. */ + stop(): void; +} diff --git a/host/src/input/key-code-table.ts b/host/src/input/key-code-table.ts new file mode 100644 index 0000000000..023af6a466 --- /dev/null +++ b/host/src/input/key-code-table.ts @@ -0,0 +1,149 @@ +/** + * `KeyboardEvent.code` → Linux `KEY_*` lookup. + * + * Matches the kernel-side `shared::input::KEY_*` constants (Linux UAPI + * verbatim — same numeric space SDL2's evdev backend would consume on + * real Linux). The W3C "UI Events KeyboardEvent code Values" spec + * defines the `KeyboardEvent.code` strings; we map each one to its + * Linux keycode where Linux has an equivalent. + * + * Returns `null` for codes we don't translate (locale-specific keys + * Linux has no UAPI for, browser-specific extensions, etc.). userspace + * stacks like libxkbcommon handle the locale layer. + */ + +const CODE_TO_KEY: Record = { + // Writing-system letters: KeyA → KEY_A = 30, etc. + KeyA: 30, KeyB: 48, KeyC: 46, KeyD: 32, KeyE: 18, KeyF: 33, + KeyG: 34, KeyH: 35, KeyI: 23, KeyJ: 36, KeyK: 37, KeyL: 38, + KeyM: 50, KeyN: 49, KeyO: 24, KeyP: 25, KeyQ: 16, KeyR: 19, + KeyS: 31, KeyT: 20, KeyU: 22, KeyV: 47, KeyW: 17, KeyX: 45, + KeyY: 21, KeyZ: 44, + + // Top-row digits: Digit1 → KEY_1 = 2, …, Digit0 → KEY_0 = 11. + Digit1: 2, Digit2: 3, Digit3: 4, Digit4: 5, Digit5: 6, + Digit6: 7, Digit7: 8, Digit8: 9, Digit9: 10, Digit0: 11, + + // Punctuation. + Minus: 12, + Equal: 13, + BracketLeft: 26, + BracketRight: 27, + Backslash: 43, + Semicolon: 39, + Quote: 40, + Backquote: 41, + Comma: 51, + Period: 52, + Slash: 53, + + // International (rare on US layouts; required for JIS/PT-BR/etc). + IntlBackslash: 86, // KEY_102ND + IntlRo: 89, // KEY_RO + IntlYen: 124, // KEY_YEN + + // Whitespace + editing. + Enter: 28, + Tab: 15, + Space: 57, + Backspace: 14, + Escape: 1, + + // Modifiers. + ShiftLeft: 42, + ShiftRight: 54, + ControlLeft: 29, + ControlRight: 97, + AltLeft: 56, + AltRight: 100, + MetaLeft: 125, + MetaRight: 126, + CapsLock: 58, + + // Function keys F1–F24. + F1: 59, F2: 60, F3: 61, F4: 62, F5: 63, F6: 64, + F7: 65, F8: 66, F9: 67, F10: 68, F11: 87, F12: 88, + F13: 183, F14: 184, F15: 185, F16: 186, F17: 187, F18: 188, + F19: 189, F20: 190, F21: 191, F22: 192, F23: 193, F24: 194, + + // Control pad. + Insert: 110, + Delete: 111, + Home: 102, + End: 107, + PageUp: 104, + PageDown: 109, + Help: 138, + + // Arrow pad. + ArrowUp: 103, + ArrowDown: 108, + ArrowLeft: 105, + ArrowRight: 106, + + // System keys. + PrintScreen: 99, // KEY_SYSRQ + ScrollLock: 70, + Pause: 119, + ContextMenu: 127, // KEY_COMPOSE — the "menu" key beside RightMeta + Power: 116, + Sleep: 142, + WakeUp: 143, + + // Numpad. + NumLock: 69, + Numpad0: 82, + Numpad1: 79, Numpad2: 80, Numpad3: 81, + Numpad4: 75, Numpad5: 76, Numpad6: 77, + Numpad7: 71, Numpad8: 72, Numpad9: 73, + NumpadAdd: 78, // KEY_KPPLUS + NumpadSubtract: 74, // KEY_KPMINUS + NumpadMultiply: 55, // KEY_KPASTERISK + NumpadDivide: 98, // KEY_KPSLASH + NumpadDecimal: 83, // KEY_KPDOT + NumpadEnter: 96, // KEY_KPENTER + NumpadEqual: 117, // KEY_KPEQUAL + NumpadComma: 121, // KEY_KPCOMMA + + // IME / CJK input. + Convert: 92, // KEY_HENKAN + NonConvert: 94, // KEY_MUHENKAN + KanaMode: 93, // KEY_KATAKANAHIRAGANA + Lang1: 122, // KEY_HANGEUL — Korean Hangul/English toggle + Lang2: 123, // KEY_HANJA — Korean Hanja conversion + Lang3: 90, // KEY_KATAKANA + Lang4: 91, // KEY_HIRAGANA + + // Audio / media. + AudioVolumeMute: 113, // KEY_MUTE + AudioVolumeDown: 114, // KEY_VOLUMEDOWN + AudioVolumeUp: 115, // KEY_VOLUMEUP + MediaPlayPause: 164, + MediaStop: 166, // KEY_STOPCD + MediaTrackNext: 163, // KEY_NEXTSONG + MediaTrackPrevious: 165, // KEY_PREVIOUSSONG + Eject: 161, // KEY_EJECTCD + + // Browser-style hotkeys (Linux UAPI subset). + BrowserRefresh: 173, + BrowserStop: 128, // KEY_STOP + LaunchApp2: 140, // KEY_CALC + + // Editing hotkeys (mostly Sun-keyboard heritage; libinput still emits). + Cut: 137, + Copy: 133, + Paste: 135, + Undo: 131, + Again: 129, + Find: 136, + Open: 134, + Props: 130, +}; + +/** Translate a `KeyboardEvent.code` string to its Linux `KEY_*` value. + * Returns `null` for codes we don't translate (locale-specific keys + * outside Linux UAPI, browser-specific extensions). */ +export function codeToKey(code: string): number | null { + const k = CODE_TO_KEY[code]; + return k === undefined ? null : k; +} diff --git a/host/src/input/node-input-source.ts b/host/src/input/node-input-source.ts new file mode 100644 index 0000000000..affefd5acd --- /dev/null +++ b/host/src/input/node-input-source.ts @@ -0,0 +1,20 @@ +/** + * `NodeInputSource` — null-source for the Node host. There's no DOM in + * Node, and the integration tests drive evdev events directly via + * `kernel.exports.kernel_input_event(…)` instead of synthesising + * KeyboardEvent / PointerEvent. The host still registers an + * `InputSource` at boot so the Node-side init path is symmetric with + * the browser-side one (CLAUDE.md §"Two hosts" — dual-host parity is + * load-bearing). `start()` and `stop()` are deliberate no-ops; no + * records are ever emitted through the registered `dispatch` callback. + */ +import type { InputSource, InputEvent } from "./input-source.js"; + +export class NodeInputSource implements InputSource { + start(_dispatch: (ev: InputEvent) => void): void { + /* intentional no-op — tests call kernel_input_event directly */ + } + stop(): void { + /* intentional no-op */ + } +} diff --git a/host/src/ioctl-contract.ts b/host/src/ioctl-contract.ts new file mode 100644 index 0000000000..2221dc18f3 --- /dev/null +++ b/host/src/ioctl-contract.ts @@ -0,0 +1,57 @@ +/** + * Host-side mirror of `shared::ioctl_contract::request_contract`. + * + * Most ioctls are keyed by an exact request number and resolve straight out + * of `IOCTL_REQUESTS`. The evdev surface is not: `EVIOCGNAME(len)` and + * `EVIOCGBIT(ev, len)` encode a caller-chosen length in the request itself, + * and `EVIOCGABS(axis)` spans 64 axes, so those resolve through + * `IOCTL_REQUEST_FAMILIES` instead. Both hosts must agree with the kernel on + * the staged byte count, so the two tables are consulted in the same order + * here as in Rust. + */ +import { + IOCTL_REQUESTS, + IOCTL_REQUEST_FAMILIES, + type IoctlRequestContract, +} from "./generated/abi"; + +function familyContract(request: number): IoctlRequestContract | undefined { + const dir = (request >>> 30) & 0x3; + const encodedSize = (request >>> 16) & 0x3fff; + const magic = (request >>> 8) & 0xff; + const nr = request & 0xff; + + const family = IOCTL_REQUEST_FAMILIES.find( + (candidate) => + candidate.dir === dir && + candidate.magic === magic && + candidate.nrFirst <= nr && + nr <= candidate.nrLast, + ); + if (!family) return undefined; + + if (family.fixedSize !== null) { + if (encodedSize !== family.fixedSize) return undefined; + } else if (family.maxCallerSize !== null) { + if (encodedSize < 1 || encodedSize > family.maxCallerSize) return undefined; + } else { + return undefined; + } + + return { + argKind: "pointer", + direction: family.direction, + wasm32Size: encodedSize, + wasm64Size: encodedSize, + }; +} + +/** + * Resolve one ioctl request to its marshalling contract, or `undefined` when + * the request is unknown and must reach the device with no staged pointer. + */ +export function resolveIoctlContract( + request: number, +): IoctlRequestContract | undefined { + return IOCTL_REQUESTS[request >>> 0] ?? familyContract(request >>> 0); +} diff --git a/host/src/kernel-worker.ts b/host/src/kernel-worker.ts index 51ecbdbd40..b2b0762d1a 100644 --- a/host/src/kernel-worker.ts +++ b/host/src/kernel-worker.ts @@ -31,6 +31,7 @@ import { WasmPosixKernel, type KernelPointer, } from "./kernel"; +import { resolveIoctlContract } from "./ioctl-contract"; import { createKernelEntryScopedInstance, invokeKernelEntrySerializedHostOperation, @@ -115,7 +116,6 @@ import { FCNTL_FLOCK_BYTES, FILE_MODES, HOST_INTERCEPTED_SYSCALLS, - IOCTL_REQUESTS, OPEN_FLAGS, PROCESS_MEMORY_PAGES_PER_THREAD_SLOT, PROCESS_MEMORY_THREAD_SLOT_CHANNEL_PRIMARY_PAGE, @@ -11904,7 +11904,7 @@ export class CentralizedKernelWorker { } if (syscallNr === SYS_IOCTL) { const request = Number(BigInt.asUintN(32, rawArgs[1]!)); - const contract = IOCTL_REQUESTS[request]; + const contract = resolveIoctlContract(request); adjustedArgs[1] = request; adjustedArgs[3] = 0; adjustedArgs[PROCESS_POINTER_WIDTH_ARG_INDEX] = pointerWidth; @@ -30117,6 +30117,57 @@ export class CentralizedKernelWorker { ); } + /** + * Push one evdev record into `/dev/input/event{0,1}` and wake any + * process blocked on `sys_read` / `sys_poll` against the device. + * The per-OFD ring caps at 1024 records; overflow latches `dropped` + * and the next read returns `SYN_DROPPED` (kernel A4/A5). Wake + * routing reuses `scheduleWakeBlockedRetries` so the existing + * pending-readers tick services event ofds too — same shape as + * `injectMouseEvent` for `/dev/input/mice`. + */ + injectInputEvent( + device: 0 | 1, + ev_type: number, + code: number, + value: number, + ): void { + this.#runOrDeferKernelEntry( + "evdev input and wake", + (entry) => { + const inject = entry.instance.exports.kernel_input_event as + | (( + device: number, + ev_type: number, + code: number, + value: number, + ) => void) + | undefined; + if (!inject) return; + inject(device, ev_type, code, value); + this.scheduleWakeBlockedRetries(entry); + }, + ); + } + + /** + * Tell the kernel the current host canvas dimensions so EVIOCGABS + * on `/dev/input/event1` reports the right `ABS_X.maximum` / + * `ABS_Y.maximum`. Idempotent; call again on canvas resize. + */ + setInputCanvasDims(width: number, height: number): void { + this.#runOrDeferKernelEntry( + "evdev canvas dimensions", + (entry) => { + const set = entry.instance.exports.kernel_set_input_canvas_dims as + | ((width: number, height: number) => void) + | undefined; + if (!set) return; + set(width, height); + }, + ); + } + /** * Drain up to `out.byteLength` bytes of PCM audio buffered in * `/dev/dsp` into `out`. Returns the number of bytes copied, always diff --git a/host/src/kernel.ts b/host/src/kernel.ts index 860feb8429..9e7724c3b8 100644 --- a/host/src/kernel.ts +++ b/host/src/kernel.ts @@ -39,8 +39,8 @@ import { runGlQuery } from "./webgl/query"; import { SubmitQueue } from "./webgl/submit-queue"; import { GlMuxer } from "./webgl/muxer"; import { drainSubmitQueue } from "./webgl/submit-drain"; +import { resolveIoctlContract } from "./ioctl-contract"; import { - IOCTL_REQUESTS, KERNEL_SCRATCH_FD_PAIR_BYTES, KERNEL_SCRATCH_SOCKLEN_BYTES, SELECT_FD_SET_BYTES, @@ -3995,7 +3995,7 @@ export class WasmPosixKernel { bufLen: number, processPointerWidth: number, ) => number; - const contract = IOCTL_REQUESTS[request >>> 0]; + const contract = resolveIoctlContract(request); const wasm32Size = contract?.wasm32Size; if (contract && wasm32Size === null) { throw new Error( diff --git a/host/src/node-kernel-host.ts b/host/src/node-kernel-host.ts index 74af94b6e5..652e008942 100644 --- a/host/src/node-kernel-host.ts +++ b/host/src/node-kernel-host.ts @@ -51,6 +51,7 @@ import { snapshotPublishedPrivilegedProgramBrowserMount, type PublishedPrivilegedProgramProduct, } from "./vfs/privileged-projection"; +import type { InputSource } from "./input/input-source"; export type { HttpRequest, HttpResponse }; @@ -688,6 +689,57 @@ export class NodeKernelHost { this.sendToWorker({ type: "kms_attach_stats", crtcId, stats }); } + /** + * Push one evdev record into the kernel's `/dev/input/event{0,1}` + * ring. Mirrors `BrowserKernel.injectInputEvent`. The Node host + * doesn't have a DOM source; tests drive evdev traffic directly + * via this entry point. + */ + injectInputEvent( + device: 0 | 1, + ev_type: number, + code: number, + value: number, + ): void { + this.sendToWorker({ + type: "input_event_inject", + device, + ev_type, + code, + value, + }); + } + + /** + * Tell the kernel the current host canvas dimensions so EVIOCGABS + * on `/dev/input/event1` reports the right `ABS_X.maximum` / + * `ABS_Y.maximum`. Mirrors `BrowserKernel.setInputCanvasDims`. + */ + setInputCanvasDims(width: number, height: number): void { + this.sendToWorker({ type: "set_input_canvas_dims", width, height }); + } + + /** + * Wire an `InputSource` into the kernel: sets canvas dims, then + * starts the source with a dispatch callback that funnels each + * emitted record through `injectInputEvent`. Mirrors + * `BrowserKernel.attachInputSource` — dual-host parity per + * CLAUDE.md §"Two hosts". + * + * On the Node host the source is typically a `NodeInputSource` + * (no-op) so the init path is symmetric with the browser; tests + * call `injectInputEvent` directly afterwards. + */ + attachInputSource( + source: InputSource, + dims: { width: number; height: number }, + ): void { + this.setInputCanvasDims(dims.width, dims.height); + source.start((ev) => + this.injectInputEvent(ev.device, ev.ev_type, ev.code, ev.value), + ); + } + /** * Send an HTTP request to a server running inside the kernel and return * the parsed response. Bypasses real TCP by using the kernel's injected diff --git a/host/src/node-kernel-protocol.ts b/host/src/node-kernel-protocol.ts index 1a263fe2cb..15d7ceac15 100644 --- a/host/src/node-kernel-protocol.ts +++ b/host/src/node-kernel-protocol.ts @@ -339,6 +339,32 @@ export interface KmsAttachStatsMessage { stats: SharedArrayBuffer; } +/** + * Main-thread → kernel-worker evdev injection. Mirrors the Browser-side + * `InputEventInjectMessage`. Under Node there is no DOM, so production + * traffic on this channel comes from tests / headless drivers; the + * Node-side `NodeInputSource` is a null-source. Routes to + * `CentralizedKernelWorker.injectInputEvent`. + */ +export interface InputEventInjectMessage { + type: "input_event_inject"; + device: 0 | 1; + ev_type: number; + code: number; + value: number; +} + +/** + * Main-thread → kernel-worker canvas-dims update. Mirrors the + * Browser-side `SetInputCanvasDimsMessage`. Sets `ABS_X.maximum` / + * `ABS_Y.maximum` reported by EVIOCGABS on `/dev/input/event1`. + */ +export interface SetInputCanvasDimsMessage { + type: "set_input_canvas_dims"; + width: number; + height: number; +} + export type MainToKernelMessage = | InitMessage | SpawnMessage @@ -371,7 +397,9 @@ export type MainToKernelMessage = | DrainSyscallTraceMessage | HttpRequestMessage | KmsAttachCanvasMessage - | KmsAttachStatsMessage; + | KmsAttachStatsMessage + | InputEventInjectMessage + | SetInputCanvasDimsMessage; // ── Kernel Worker → Main Thread ── diff --git a/host/src/node-kernel-worker-entry.ts b/host/src/node-kernel-worker-entry.ts index 3c39f7e331..ba65a0379c 100644 --- a/host/src/node-kernel-worker-entry.ts +++ b/host/src/node-kernel-worker-entry.ts @@ -3872,6 +3872,12 @@ port.on("message", (msg: MainToKernelMessage) => { case "kms_attach_stats": kernelWorker.attachKmsStats(msg.crtcId, msg.stats); break; + case "input_event_inject": + kernelWorker.injectInputEvent(msg.device, msg.ev_type, msg.code, msg.value); + break; + case "set_input_canvas_dims": + kernelWorker.setInputCanvasDims(msg.width, msg.height); + break; default: { const exhaustive: never = msg; void exhaustive; diff --git a/host/test/browser-input-source.test.ts b/host/test/browser-input-source.test.ts new file mode 100644 index 0000000000..d0d682c3af --- /dev/null +++ b/host/test/browser-input-source.test.ts @@ -0,0 +1,253 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { BrowserInputSource } from "../src/input/browser-input-source.js"; +import type { InputEvent } from "../src/input/input-source.js"; + +/** + * Minimal EventTarget stub. We don't pull in jsdom/happy-dom — these + * tests cover translation logic, not DOM semantics. `fire(name, ev)` + * synchronously invokes every listener bound for that event name. + */ +class FakeTarget implements EventTarget { + private listeners = new Map(); + addEventListener(name: string, l: EventListenerOrEventListenerObject | null) { + if (typeof l !== "function") return; + const arr = this.listeners.get(name) ?? []; + arr.push(l); + this.listeners.set(name, arr); + } + removeEventListener(name: string, l: EventListenerOrEventListenerObject | null) { + if (typeof l !== "function") return; + const arr = (this.listeners.get(name) ?? []).filter((x) => x !== l); + this.listeners.set(name, arr); + } + dispatchEvent(_e: Event): boolean { + return true; + } + fire(name: string, ev: object): void { + for (const l of this.listeners.get(name) ?? []) l(ev as Event); + } + count(name: string): number { + return (this.listeners.get(name) ?? []).length; + } +} + +describe("BrowserInputSource", () => { + let target: FakeTarget; + let doc: FakeTarget & { pointerLockElement: Element | null }; + let recorded: InputEvent[]; + let src: BrowserInputSource; + + beforeEach(() => { + target = new FakeTarget(); + doc = Object.assign(new FakeTarget(), { + pointerLockElement: null as Element | null, + }); + vi.stubGlobal("document", doc); + recorded = []; + src = new BrowserInputSource(target); + src.start((ev) => recorded.push(ev)); + }); + + afterEach(() => { + src.stop(); + vi.unstubAllGlobals(); + }); + + it("keydown emits EV_KEY(KEY_A, 1) then SYN_REPORT on the keyboard device", () => { + target.fire("keydown", { + code: "KeyA", + repeat: false, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 0, ev_type: 0x01, code: 30, value: 1 }, + { device: 0, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("repeat keydown emits value=2 (Linux autorepeat convention)", () => { + target.fire("keydown", { + code: "Space", + repeat: true, + preventDefault() {}, + }); + expect(recorded[0]).toEqual({ + device: 0, + ev_type: 0x01, + code: 57, + value: 2, + }); + }); + + it("unknown KeyboardEvent.code is ignored and preventDefault is not called", () => { + let prevented = false; + target.fire("keydown", { + code: "Hyper", + repeat: false, + preventDefault() { + prevented = true; + }, + }); + expect(recorded).toEqual([]); + expect(prevented).toBe(false); + }); + + it("keyup emits EV_KEY(code, 0) then SYN_REPORT", () => { + target.fire("keyup", { + code: "Escape", + repeat: false, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 0, ev_type: 0x01, code: 1, value: 0 }, + { device: 0, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("pointermove without pointer lock emits ABS_X/ABS_Y absolute coords", () => { + target.fire("pointermove", { + offsetX: 123.7, + offsetY: 45, + movementX: 0, + movementY: 0, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x03, code: 0x00, value: 124 }, + { device: 1, ev_type: 0x03, code: 0x01, value: 45 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("pointermove with pointer lock active emits REL_X/REL_Y deltas", () => { + doc.pointerLockElement = {} as Element; + target.fire("pointermove", { + offsetX: 0, + offsetY: 0, + movementX: -3, + movementY: 7, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x00, value: -3 }, + { device: 1, ev_type: 0x02, code: 0x01, value: 7 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("pointermove in lock with zero movement on one axis skips that axis", () => { + doc.pointerLockElement = {} as Element; + target.fire("pointermove", { + offsetX: 0, + offsetY: 0, + movementX: 5, + movementY: 0, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x00, value: 5 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("pointerdown emits BTN_LEFT/MIDDLE/RIGHT for each mouse button", () => { + target.fire("pointerdown", { button: 0 }); + target.fire("pointerdown", { button: 1 }); + target.fire("pointerdown", { button: 2 }); + const codes = recorded.filter((e) => e.ev_type === 0x01).map((e) => e.code); + expect(codes).toEqual([0x110, 0x112, 0x111]); + }); + + it("pointerup emits BTN_LEFT release", () => { + target.fire("pointerup", { button: 0 }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x01, code: 0x110, value: 0 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("pointerdown for unknown button (e.g. side button) drops the event", () => { + target.fire("pointerdown", { button: 3 }); + expect(recorded).toEqual([]); + }); + + it("wheel deltaMode=PIXEL with ±120 chunks normalises to ±1 tick", () => { + target.fire("wheel", { + deltaMode: 0, + deltaX: 0, + deltaY: 120, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x08, value: -1 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("wheel deltaMode=LINE with -3 lines normalises to +3 ticks", () => { + target.fire("wheel", { + deltaMode: 1, + deltaX: 0, + deltaY: -3, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x08, value: 3 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("wheel small-but-nonzero pixel delta clamps to ±1 tick (trackpad)", () => { + target.fire("wheel", { + deltaMode: 0, + deltaX: 0, + deltaY: 1, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x08, value: -1 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("wheel horizontal-only emits REL_HWHEEL and frames", () => { + target.fire("wheel", { + deltaMode: 0, + deltaX: 240, + deltaY: 0, + preventDefault() {}, + }); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x02, code: 0x06, value: 2 }, + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("wheel with zero delta emits no records", () => { + target.fire("wheel", { + deltaMode: 0, + deltaX: 0, + deltaY: 0, + preventDefault() {}, + }); + expect(recorded).toEqual([]); + }); + + it("pointerlockchange emits a bare SYN_REPORT on the pointer device", () => { + doc.fire("pointerlockchange", {}); + expect(recorded).toEqual([ + { device: 1, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); + + it("stop() removes all listeners; subsequent fires emit nothing", () => { + src.stop(); + expect(target.count("keydown")).toBe(0); + expect(target.count("pointermove")).toBe(0); + expect(doc.count("pointerlockchange")).toBe(0); + target.fire("keydown", { + code: "KeyA", + repeat: false, + preventDefault() {}, + }); + doc.fire("pointerlockchange", {}); + expect(recorded).toEqual([]); + }); +}); diff --git a/host/test/input-attach-source.test.ts b/host/test/input-attach-source.test.ts new file mode 100644 index 0000000000..533bd012d6 --- /dev/null +++ b/host/test/input-attach-source.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it, vi } from "vitest"; +import { NodeKernelHost } from "../src/node-kernel-host.js"; +import { NodeInputSource } from "../src/input/node-input-source.js"; +import type { MainToKernelMessage } from "../src/node-kernel-protocol.js"; + +/** + * B4 dual-host parity test. Covers the Node side of `attachInputSource` + * — the browser side is mirror-imaged in `BrowserKernel` and exercised + * end-to-end at Phase C via Playwright. The contract verified here: + * + * 1. `setInputCanvasDims` runs exactly once with the requested dims. + * 2. `source.start(dispatch)` runs exactly once. + * 3. The dispatch handed to `start` funnels each emitted record + * through `injectInputEvent` (→ `input_event_inject` worker msg). + * + * We bypass `init()` (which spawns a worker_thread + waits for ready + * over a worker channel) and stub `sendToWorker` directly. The + * constructor only stores options, so a bare `new NodeKernelHost()` + * is safe to construct. + */ +describe("NodeKernelHost.attachInputSource", () => { + it("sets canvas dims, starts the source, and routes dispatch to injectInputEvent", () => { + const host = new NodeKernelHost(); + const sent: MainToKernelMessage[] = []; + (host as unknown as { sendToWorker: (m: MainToKernelMessage) => void }) + .sendToWorker = (m) => sent.push(m); + + const source = new NodeInputSource(); + const startSpy = vi.spyOn(source, "start"); + + host.attachInputSource(source, { width: 1024, height: 768 }); + + // 1. canvas dims went out exactly once with the right values. + const dims = sent.filter((m) => m.type === "set_input_canvas_dims"); + expect(dims).toEqual([ + { type: "set_input_canvas_dims", width: 1024, height: 768 }, + ]); + + // 2. source.start was called exactly once with a function arg. + expect(startSpy).toHaveBeenCalledTimes(1); + const dispatch = startSpy.mock.calls[0]?.[0]; + expect(typeof dispatch).toBe("function"); + + // 3. The dispatch routes each record through input_event_inject. + dispatch!({ device: 0, ev_type: 0x01, code: 30, value: 1 }); + dispatch!({ device: 1, ev_type: 0x02, code: 0x00, value: -5 }); + + const injects = sent.filter((m) => m.type === "input_event_inject"); + expect(injects).toEqual([ + { + type: "input_event_inject", + device: 0, + ev_type: 0x01, + code: 30, + value: 1, + }, + { + type: "input_event_inject", + device: 1, + ev_type: 0x02, + code: 0x00, + value: -5, + }, + ]); + }); + + it("setInputCanvasDims posts the worker message standalone", () => { + const host = new NodeKernelHost(); + const sent: MainToKernelMessage[] = []; + (host as unknown as { sendToWorker: (m: MainToKernelMessage) => void }) + .sendToWorker = (m) => sent.push(m); + + host.setInputCanvasDims(640, 480); + host.setInputCanvasDims(800, 600); + + expect(sent).toEqual([ + { type: "set_input_canvas_dims", width: 640, height: 480 }, + { type: "set_input_canvas_dims", width: 800, height: 600 }, + ]); + }); + + it("injectInputEvent posts the worker message standalone", () => { + const host = new NodeKernelHost(); + const sent: MainToKernelMessage[] = []; + (host as unknown as { sendToWorker: (m: MainToKernelMessage) => void }) + .sendToWorker = (m) => sent.push(m); + + host.injectInputEvent(0, 0x01, 1, 0); + + expect(sent).toEqual([ + { + type: "input_event_inject", + device: 0, + ev_type: 0x01, + code: 1, + value: 0, + }, + ]); + }); +}); diff --git a/host/test/input-evdev.test.ts b/host/test/input-evdev.test.ts new file mode 100644 index 0000000000..db59c17527 --- /dev/null +++ b/host/test/input-evdev.test.ts @@ -0,0 +1,185 @@ +/** + * B5 end-to-end gate for the evdev path. Spawns `input-evdev-smoke` + * inside the centralized kernel, then drives `kernel_input_event` + * from the host via `NodeKernelHost.injectInputEvent` — same shape a + * real `BrowserInputSource` will use at Phase C. + * + * The fixture gates each phase on a stdin byte so the host injects + * events AFTER the fixture has opened the matching device. `push_event` + * fans out at injection time, so an OFD must already exist. + */ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { NodeKernelHost } from "../src/node-kernel-host"; +import { tryResolveBinary } from "../src/binary-resolver"; + +const fixtureBinary = tryResolveBinary("programs/input-evdev-smoke.wasm"); + +const CANVAS_W = 1024; +const CANVAS_H = 768; + +const EV_SYN = 0x00; +const EV_KEY = 0x01; +const EV_REL = 0x02; +const SYN_REPORT = 0x00; +const SYN_DROPPED = 0x03; +const KEY_A = 30; +const REL_X = 0x00; +const RING_CAP = 1024; + +const KICK = new Uint8Array([0x0a]); + +async function waitFor( + stdoutRef: { value: string }, + needle: string, + timeoutMs: number, +): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (stdoutRef.value.includes(needle)) return; + await new Promise((r) => setTimeout(r, 5)); + } + throw new Error( + `Timed out waiting for ${JSON.stringify(needle)}.\n` + + `stdout so far:\n${stdoutRef.value}`, + ); +} + +describe("evdev — end-to-end key + pointer + ring overflow", () => { + it.skipIf(!fixtureBinary)( + "round-trips keyboard + pointer events and surfaces SYN_DROPPED on overflow", + async () => { + const fileBuf = readFileSync(fixtureBinary!); + const programBytes = fileBuf.buffer.slice( + fileBuf.byteOffset, + fileBuf.byteOffset + fileBuf.byteLength, + ); + + const stdout = { value: "" }; + const stderr = { value: "" }; + + const host = new NodeKernelHost({ + onStdout: (_pid, data) => { + stdout.value += new TextDecoder().decode(data); + }, + onStderr: (_pid, data) => { + stderr.value += new TextDecoder().decode(data); + }, + }); + + try { + await host.init(); + host.setInputCanvasDims(CANVAS_W, CANVAS_H); + + let pid = 0; + const exitPromise = host.spawn(programBytes, ["input-evdev-smoke"], { + onStarted: (p) => { + pid = p; + }, + }); + + // Phase 1 — keyboard. + await waitFor(stdout, "READY:kbd\n", 10_000); + host.injectInputEvent(0, EV_KEY, KEY_A, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + host.appendStdinData(pid, KICK); + + // Phase 2 — pointer. + await waitFor(stdout, "READY:ptr\n", 10_000); + host.injectInputEvent(1, EV_REL, REL_X, 5); + host.injectInputEvent(1, EV_SYN, SYN_REPORT, 0); + host.appendStdinData(pid, KICK); + + // Phase 3 — overflow on event0. Push 1100 KEY_A toggles; the + // ring caps at 1024, latches dropped, and the next read + // prepends a synthesised SYN_DROPPED. + await waitFor(stdout, "READY:overflow\n", 10_000); + for (let i = 0; i < 1100; i++) { + host.injectInputEvent(0, EV_KEY, KEY_A, i & 1); + } + host.appendStdinData(pid, KICK); + + const exitCode = await Promise.race([ + exitPromise, + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error( + `fixture timed out\nstdout:\n${stdout.value}\nstderr:\n${stderr.value}`, + ), + ), + 30_000, + ), + ), + ]); + expect( + exitCode, + `stdout=${stdout.value}\nstderr=${stderr.value}`, + ).toBe(0); + + // EVIOCGNAME returns the kernel-supplied device names. + expect(stdout.value).toContain("kbd_name=wpk virtual keyboard"); + + // EVIOCGABS(ABS_X) on event1 reports canvas_w - 1. + expect(stdout.value).toMatch( + new RegExp(`ptr_abs_x_max=${CANVAS_W - 1}\\b`), + ); + + // Phase 1: KEY_A down + SYN_REPORT, in that order, with + // monotonic-non-decreasing CLOCK_MONOTONIC timestamps. + const kev0 = stdout.value.match( + /kbd_ev0 type=(\d+) code=(\d+) value=(-?\d+) tv_sec=(-?\d+) tv_usec=(-?\d+)/, + ); + const kev1 = stdout.value.match( + /kbd_ev1 type=(\d+) code=(\d+) value=(-?\d+) tv_sec=(-?\d+) tv_usec=(-?\d+)/, + ); + expect(kev0, `missing kbd_ev0 in:\n${stdout.value}`).not.toBeNull(); + expect(kev1, `missing kbd_ev1 in:\n${stdout.value}`).not.toBeNull(); + expect(parseInt(kev0![1], 10)).toBe(EV_KEY); + expect(parseInt(kev0![2], 10)).toBe(KEY_A); + expect(parseInt(kev0![3], 10)).toBe(1); + expect(parseInt(kev1![1], 10)).toBe(EV_SYN); + expect(parseInt(kev1![2], 10)).toBe(SYN_REPORT); + const ts0 = + BigInt(kev0![4]) * 1_000_000n + BigInt(parseInt(kev0![5], 10)); + const ts1 = + BigInt(kev1![4]) * 1_000_000n + BigInt(parseInt(kev1![5], 10)); + expect(ts1 >= ts0).toBe(true); + + // Phase 2: REL_X=+5 + SYN_REPORT. + const pev0 = stdout.value.match( + /ptr_ev0 type=(\d+) code=(\d+) value=(-?\d+)/, + ); + const pev1 = stdout.value.match( + /ptr_ev1 type=(\d+) code=(\d+) value=(-?\d+)/, + ); + expect(pev0, `missing ptr_ev0 in:\n${stdout.value}`).not.toBeNull(); + expect(pev1, `missing ptr_ev1 in:\n${stdout.value}`).not.toBeNull(); + expect(parseInt(pev0![1], 10)).toBe(EV_REL); + expect(parseInt(pev0![2], 10)).toBe(REL_X); + expect(parseInt(pev0![3], 10)).toBe(5); + expect(parseInt(pev1![1], 10)).toBe(EV_SYN); + expect(parseInt(pev1![2], 10)).toBe(SYN_REPORT); + + // Phase 3 overflow: SYN_DROPPED first, then the surviving 1024 + // ring records (the most recent of the 1100 pushed before the + // ring saturated). Total = 1 synth + 1024 = 1025. + const ov = stdout.value.match( + /ov_count=(\d+) ov_syn_dropped_at=(-?\d+) ov_real=(\d+) ov_last_type=(\d+) ov_last_code=(\d+)/, + ); + expect(ov, `missing ov_ line in:\n${stdout.value}`).not.toBeNull(); + expect(parseInt(ov![1], 10)).toBe(RING_CAP + 1); + expect(parseInt(ov![2], 10)).toBe(0); + expect(parseInt(ov![3], 10)).toBe(RING_CAP); + // Last surviving record is an EV_KEY/KEY_A (the toggles we + // pushed), not a stray SYN. + expect(parseInt(ov![4], 10)).toBe(EV_KEY); + expect(parseInt(ov![5], 10)).toBe(KEY_A); + } finally { + await host.destroy().catch(() => {}); + } + }, + 60_000, + ); +}); diff --git a/host/test/input-source.test.ts b/host/test/input-source.test.ts new file mode 100644 index 0000000000..fd6d1ac239 --- /dev/null +++ b/host/test/input-source.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import type { + InputEvent, + InputSource, +} from "../src/input/input-source.js"; + +describe("InputSource interface", () => { + it("admits a minimal stub source that round-trips events through dispatch", () => { + const recorded: InputEvent[] = []; + + class StubSource implements InputSource { + private dispatch: ((ev: InputEvent) => void) | null = null; + start(dispatch: (ev: InputEvent) => void): void { + this.dispatch = dispatch; + } + stop(): void { + this.dispatch = null; + } + emit(ev: InputEvent): void { + this.dispatch?.(ev); + } + } + + const src = new StubSource(); + src.start((ev) => recorded.push(ev)); + src.emit({ device: 0, ev_type: 0x01, code: 30, value: 1 }); + src.emit({ device: 0, ev_type: 0x00, code: 0, value: 0 }); + src.stop(); + src.emit({ device: 1, ev_type: 0x02, code: 0, value: 5 }); + + expect(recorded).toEqual([ + { device: 0, ev_type: 0x01, code: 30, value: 1 }, + { device: 0, ev_type: 0x00, code: 0, value: 0 }, + ]); + }); +}); diff --git a/host/test/ioctl-contract.test.ts b/host/test/ioctl-contract.test.ts new file mode 100644 index 0000000000..bcb2596148 --- /dev/null +++ b/host/test/ioctl-contract.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; + +import { IOCTL_REQUESTS } from "../src/generated/abi"; +import { resolveIoctlContract } from "../src/ioctl-contract"; + +/** Builds the same encoding the musl `_IOC` macros produce. */ +function evioc(dir: number, nr: number, size: number): number { + return ((dir << 30) | (size << 16) | (0x45 << 8) | nr) >>> 0; +} + +const EVIOCGNAME_NR = 0x06; +const EVIOCGBIT_NR_BASE = 0x20; +const EVIOCGABS_NR_BASE = 0x40; +const MAX_CALLER_LENGTH = 256; + +describe("ioctl contract resolution", () => { + it("resolves exact-numbered requests out of the generated table", () => { + expect(resolveIoctlContract(0x540b)).toEqual(IOCTL_REQUESTS[0x540b]); + expect(resolveIoctlContract(0x8004_4501)).toMatchObject({ + argKind: "pointer", + direction: "out", + wasm32Size: 4, + }); + expect(resolveIoctlContract(0x4004_4590)).toMatchObject({ + argKind: "scalar-i32", + }); + }); + + it("resolves EVIOCGABS on every axis at the absinfo size", () => { + for (let axis = 0; axis < 64; axis++) { + expect( + resolveIoctlContract(evioc(2, EVIOCGABS_NR_BASE + axis, 24)), + `axis ${axis}`, + ).toEqual({ + argKind: "pointer", + direction: "out", + wasm32Size: 24, + wasm64Size: 24, + }); + } + }); + + it("rejects an EVIOCGABS request sized as anything but absinfo", () => { + expect(resolveIoctlContract(evioc(2, EVIOCGABS_NR_BASE, 16))).toBeUndefined(); + }); + + it("carries the caller's length for EVIOCGNAME and EVIOCGBIT", () => { + for (const size of [1, 32, MAX_CALLER_LENGTH]) { + expect( + resolveIoctlContract(evioc(2, EVIOCGNAME_NR, size))?.wasm32Size, + ).toBe(size); + expect( + resolveIoctlContract(evioc(2, EVIOCGBIT_NR_BASE + 1, size))?.wasm32Size, + ).toBe(size); + } + }); + + it("rejects a zero or oversized caller length", () => { + for (const nr of [EVIOCGNAME_NR, EVIOCGBIT_NR_BASE]) { + expect(resolveIoctlContract(evioc(2, nr, 0))).toBeUndefined(); + expect( + resolveIoctlContract(evioc(2, nr, MAX_CALLER_LENGTH + 1)), + ).toBeUndefined(); + } + }); + + it("ignores a foreign magic or a write direction", () => { + const foreignMagic = + ((2 << 30) | (24 << 16) | (0x44 << 8) | EVIOCGABS_NR_BASE) >>> 0; + expect(resolveIoctlContract(foreignMagic)).toBeUndefined(); + expect( + resolveIoctlContract(evioc(1, EVIOCGABS_NR_BASE, 24)), + ).toBeUndefined(); + }); + + it("leaves an unknown request unresolved", () => { + expect(resolveIoctlContract(0xdead_0000)).toBeUndefined(); + }); +}); diff --git a/host/test/kernel-clone-exit-entry.test.ts b/host/test/kernel-clone-exit-entry.test.ts index 0f0547a3b8..fcdd49d99e 100644 --- a/host/test/kernel-clone-exit-entry.test.ts +++ b/host/test/kernel-clone-exit-entry.test.ts @@ -39,7 +39,9 @@ const KERNEL_EXPORT_NAMES = [ "kernel_get_process_state", "kernel_handle_channel", "kernel_inject_mouse_event", + "kernel_input_event", "kernel_set_current_tid", + "kernel_set_input_canvas_dims", "kernel_take_process_timer_cleanup", "kernel_thread_exit", ] as const; @@ -104,7 +106,9 @@ function makeHarness( kernel_get_process_state: () => PROCESS_STATE_EXITED, kernel_handle_channel: () => 0, kernel_inject_mouse_event: () => 0, + kernel_input_event: () => 0, kernel_set_current_tid: () => 0, + kernel_set_input_canvas_dims: () => 0, kernel_take_process_timer_cleanup: emptyProcessTimerCleanup(kernelMemory), kernel_thread_exit: () => 0, ...implementations, @@ -401,6 +405,47 @@ describe("clone and exit entry authority", () => { }, ); + it("defers evdev ingress raised from a detached host callback", async () => { + const order: string[] = []; + const inputEvent = vi.fn(() => { + order.push("queued evdev record"); + return 0; + }); + const canvasDims = vi.fn(() => { + order.push("queued canvas dimensions"); + return 0; + }); + let harness!: LifecycleHarness; + const onExit = vi.fn(() => { + order.push("host exit callback"); + harness.worker.setInputCanvasDims(1024, 768); + harness.worker.injectInputEvent(0, 0x01, 30, 1); + expect(canvasDims).not.toHaveBeenCalled(); + expect(inputEvent).not.toHaveBeenCalled(); + }); + harness = makeHarness( + 4, + { onExit }, + { + kernel_input_event: inputEvent, + kernel_set_input_canvas_dims: canvasDims, + }, + ); + writeSyscall(harness.channel, ABI_SYSCALLS.Exit, [7n]); + + harness.worker.handleSyscall(harness.channel); + + await flushLifecycleContinuations(); + + expect(canvasDims).toHaveBeenCalledExactlyOnceWith(1024, 768); + expect(inputEvent).toHaveBeenCalledExactlyOnceWith(0, 0x01, 30, 1); + expect(order).toEqual([ + "host exit callback", + "queued canvas dimensions", + "queued evdev record", + ]); + }); + it("keeps host exit state private when Rust cannot prove the committed status", async () => { const onExit = vi.fn(); const onKernelFatal = vi.fn(); diff --git a/host/test/node-input-source.test.ts b/host/test/node-input-source.test.ts new file mode 100644 index 0000000000..e56b0d5b66 --- /dev/null +++ b/host/test/node-input-source.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { NodeInputSource } from "../src/input/node-input-source.js"; +import type { InputEvent } from "../src/input/input-source.js"; + +describe("NodeInputSource", () => { + it("start() registers but emits no records; stop() is a no-op too", () => { + const recorded: InputEvent[] = []; + const src = new NodeInputSource(); + src.start((ev) => recorded.push(ev)); + src.stop(); + expect(recorded).toEqual([]); + }); + + it("can be started + stopped repeatedly without throwing", () => { + const src = new NodeInputSource(); + src.start(() => {}); + src.stop(); + src.start(() => {}); + src.stop(); + expect(true).toBe(true); + }); +}); diff --git a/host/test/support/kernel-scratch-instance.ts b/host/test/support/kernel-scratch-instance.ts index c7375554fc..22e5aaa975 100644 --- a/host/test/support/kernel-scratch-instance.ts +++ b/host/test/support/kernel-scratch-instance.ts @@ -215,6 +215,10 @@ function signatures( // genuine Wasm function and the exact gated export lookup. result: i32, }, + kernel_input_event: { + parameters: [i32, i32, i32, i32], + result: i32, + }, kernel_ioctl: { parameters: [i32, i32, pointer, i32, i32], result: i32, @@ -457,6 +461,10 @@ function signatures( parameters: [i32, i32], result: i32, }, + kernel_set_input_canvas_dims: { + parameters: [i32, i32], + result: i32, + }, kernel_set_max_addr: { parameters: [i32, pointer], result: i32, diff --git a/libc/glue/abi_constants.h b/libc/glue/abi_constants.h index 69f3a297b7..379c72ee5d 100644 --- a/libc/glue/abi_constants.h +++ b/libc/glue/abi_constants.h @@ -259,6 +259,10 @@ WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x00008905u: return pointer_width == 4u ? 4u : pointer_width == 8u ? 4u : +WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; + case 0x40044590u: +return pointer_width == 4u ? 0u : +pointer_width == 8u ? 0u : WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x40045004u: return pointer_width == 4u ? 4u : @@ -275,6 +279,10 @@ WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x40086409u: return pointer_width == 4u ? 8u : pointer_width == 8u ? 8u : +WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; + case 0x80044501u: +return pointer_width == 4u ? 4u : +pointer_width == 8u ? 4u : WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x80045002u: return pointer_width == 4u ? 4u : @@ -311,6 +319,10 @@ WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x80045430u: return pointer_width == 4u ? 4u : pointer_width == 8u ? 4u : +WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; + case 0x80084502u: +return pointer_width == 4u ? 8u : +pointer_width == 8u ? 8u : WASM_POSIX_IOCTL_UNSUPPORTED_SIZE; case 0x80085013u: return pointer_width == 4u ? 8u : diff --git a/libc/musl-overlay/include/linux/input-event-codes.h b/libc/musl-overlay/include/linux/input-event-codes.h new file mode 100644 index 0000000000..31d318d4e5 --- /dev/null +++ b/libc/musl-overlay/include/linux/input-event-codes.h @@ -0,0 +1,223 @@ +/* + * Minimal for kandelo. + * + * Mirrors the constant set kandelo's kernel-side `shared::input` module + * defines in `crates/shared/src/lib.rs` — same numeric space SDL2's + * evdev backend (and any Linux userspace) would consume on real Linux. + * The KEY_* range covers what Chrome / Firefox / WebKit emit through + * `KeyboardEvent.code`; values >248 (KEY_BUTTONCONFIG, KEY_VENDOR + * range, etc.) are not browser-reachable and aren't vendored. + * + * Any change here is part of the kernel ABI — bump ABI_VERSION. + */ +#ifndef _LINUX_INPUT_EVENT_CODES_H +#define _LINUX_INPUT_EVENT_CODES_H 1 + +/* --- Event types (struct input_event.type) --------------------------- */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 + +/* --- SYN codes (struct input_event.code when type == EV_SYN) --------- */ + +#define SYN_REPORT 0 +#define SYN_DROPPED 3 + +/* --- KEY_* codes (verbatim from upstream linux/input-event-codes.h) -- */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 +#define KEY_SCALE 120 +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 +#define KEY_STOP 128 +#define KEY_AGAIN 129 +#define KEY_PROPS 130 +#define KEY_UNDO 131 +#define KEY_FRONT 132 +#define KEY_COPY 133 +#define KEY_OPEN 134 +#define KEY_PASTE 135 +#define KEY_FIND 136 +#define KEY_CUT 137 +#define KEY_HELP 138 +#define KEY_MENU 139 +#define KEY_CALC 140 +#define KEY_SLEEP 142 +#define KEY_WAKEUP 143 +#define KEY_EJECTCD 161 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_REFRESH 173 +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MICMUTE 248 + +/* --- BTN_* codes (button class; reuse the EV_KEY event type) --------- */ + +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 + +/* --- REL_* codes (relative axes; EV_REL records carry these) --------- */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_HWHEEL 0x06 +#define REL_WHEEL 0x08 + +/* --- ABS_* codes (absolute axes; EV_ABS records carry these) --------- */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 + +/* --- BUS_* constants (subset) ---------------------------------------- */ + +/* `BUS_VIRTUAL` — closest match for a kernel-synthesised device (Linux + * uses this for `uinput`-backed devices). */ +#define BUS_VIRTUAL 0x06 + +#endif /* _LINUX_INPUT_EVENT_CODES_H */ diff --git a/libc/musl-overlay/include/linux/input.h b/libc/musl-overlay/include/linux/input.h new file mode 100644 index 0000000000..bd33437ca8 --- /dev/null +++ b/libc/musl-overlay/include/linux/input.h @@ -0,0 +1,84 @@ +/* + * Subset of matching what crates/shared/src/lib.rs::input + * marshals. Force-feedback, autorepeat, MT slots, and the rest of the + * Linux UAPI surface are intentionally omitted — kandelo doesn't + * implement them. + * + * Any change here is part of the kernel ABI — bump ABI_VERSION. + */ +#ifndef _LINUX_INPUT_H +#define _LINUX_INPUT_H 1 + +#include +#include +#include +#include + +/* Linux UAPI naming. Defined inline rather than dragging in a separate + * stub. Guard each so a parent project that already + * defines them via its own doesn't see a redefinition. */ +#ifndef __u8 +typedef uint8_t __u8; +#endif +#ifndef __u16 +typedef uint16_t __u16; +#endif +#ifndef __u32 +typedef uint32_t __u32; +#endif +#ifndef __s8 +typedef int8_t __s8; +#endif +#ifndef __s16 +typedef int16_t __s16; +#endif +#ifndef __s32 +typedef int32_t __s32; +#endif + +/* `struct input_event` on wasm32-musl. Total 24 bytes: + * struct timeval (i64 tv_sec + i32 tv_usec + 4B trailing pad to + * re-align to 8) = 16 bytes, + * __u16 type + __u16 code + __s32 value = 8. + * Matches `shared::input::WpkInputEvent`. */ +struct input_event { + struct timeval time; + __u16 type; + __u16 code; + __s32 value; +}; + +/* Returned by EVIOCGID. Total 8 bytes. */ +struct input_id { + __u16 bustype; + __u16 vendor; + __u16 product; + __u16 version; +}; + +/* Returned by EVIOCGABS(axis). Total 24 bytes. The kernel reports + * `maximum = canvas_dim - 1`, `resolution = 1` unit per pixel; other + * fields are zero. */ +struct input_absinfo { + __s32 value; + __s32 minimum; + __s32 maximum; + __s32 fuzz; + __s32 flat; + __s32 resolution; +}; + +/* --- ioctl numbers ('E' magic, Linux UAPI verbatim) ------------------ + * + * The kernel A3 dispatch matches on (dir, magic, nr); the `size` field + * (bits 16..29) is informational on the userspace side — the kernel + * re-computes the buffer length from `size` at dispatch time. */ + +#define EVIOCGVERSION _IOR('E', 0x01, int) +#define EVIOCGID _IOR('E', 0x02, struct input_id) +#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) +#define EVIOCGBIT(ev, len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) +#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) +#define EVIOCGRAB _IOW('E', 0x90, int) + +#endif /* _LINUX_INPUT_H */ diff --git a/packages/registry/espeak-ng/build-espeak-ng.sh b/packages/registry/espeak-ng/build-espeak-ng.sh new file mode 100755 index 0000000000..19ea104ff3 --- /dev/null +++ b/packages/registry/espeak-ng/build-espeak-ng.sh @@ -0,0 +1,335 @@ +#!/usr/bin/env bash +# +# Build espeak-ng for wasm32-posix-kernel. +# +# Two-pass build: +# +# 1. Native build of espeak-ng on the host. Its binary compiles the +# phoneme + intonation data out of phsource/ + dictsource/ via +# the --compile-* commands, and that pass writes the data dir +# this package ships. +# 2. Cross build of espeak-ng for wasm32, linked against upstream +# pcaudiolib built with only its OSS backend. That backend opens +# /dev/dsp, Kandelo's low-level audio API, so the resulting +# espeak-ng.wasm produces audible speech inside the kandelo +# browser preset. Neither source tree is patched. +# +# Honors the dep-resolver build-script contract — see +# packages/registry/libxml2/build-libxml2.sh for the pattern. +# +# Output layout: +# +# $INSTALL_DIR/ +# bin/espeak-ng.wasm (executable wasm binary) +# share/espeak-ng-data/ (phoneme + voice data dir, +# compiled by the native bin) +# share/espeak-ng-data.zip (that dir packed as the +# declared runtime file) +# +# Default install dir for legacy / ad-hoc invocation is +# ./espeak-ng-install/ next to this script. + +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$HERE/../../.." && pwd)" +PCAUDIO_SRC_DIR="$HERE/pcaudiolib-src" +SRC_DIR="$HERE/espeak-ng-src" + +# --- Resolver-contract env / legacy fallbacks --- +INSTALL_DIR="${WASM_POSIX_DEP_OUT_DIR:-$HERE/espeak-ng-install}" + +# --- Upstream source pins --- +# espeak-ng publishes no source archive as a release asset, so its pin is +# the tag archive. pcaudiolib publishes one. +ESPEAK_VERSION="${WASM_POSIX_DEP_VERSION:-1.52.0}" +ESPEAK_SOURCE_URL="${WASM_POSIX_DEP_SOURCE_URL:-https://github.com/espeak-ng/espeak-ng/archive/refs/tags/${ESPEAK_VERSION}.tar.gz}" +ESPEAK_SOURCE_SHA256="${WASM_POSIX_DEP_SOURCE_SHA256:-bb4338102ff3b49a81423da8a1a158b420124b055b60fa76cfb4b18677130a23}" + +PCAUDIO_VERSION="1.3" +PCAUDIO_SOURCE_URL="https://github.com/espeak-ng/pcaudiolib/releases/download/${PCAUDIO_VERSION}/pcaudiolib-${PCAUDIO_VERSION}.tar.gz" +PCAUDIO_SOURCE_SHA256="e8bd15f460ea171ccd0769ea432e188532a7fb27fa73ec2d526088a082abaaad" + +# Languages to compile. The full upstream list is ~80 languages and +# bloats the VFS image by ~25 MB. Default to English-only for the demo; +# override at build time with e.g. ESPEAK_LANG_LIST="en de fr". +ESPEAK_LANG_LIST="${ESPEAK_LANG_LIST:-en}" + +# --- SDK + sysroot --- +# Source this worktree's SDK directly instead of relying on `npm link`. +source "$REPO_ROOT/sdk/activate.sh" +SYSROOT="${WASM_POSIX_SYSROOT:-$REPO_ROOT/sysroot}" +export WASM_POSIX_SYSROOT="$SYSROOT" + +if ! command -v wasm32posix-cc >/dev/null; then + echo "ERROR: wasm32posix-cc not found on PATH after sourcing sdk/activate.sh." >&2 + exit 1 +fi +if [ ! -f "$SYSROOT/lib/libc.a" ]; then + echo "ERROR: kandelo sysroot not built at $SYSROOT. Run bash scripts/build-musl.sh first." >&2 + exit 1 +fi +for tool in cmake curl tar shasum python3; do + command -v "$tool" >/dev/null || { + echo "ERROR: required build tool not found: $tool" >&2 + exit 1 + } +done + +# --- Fetch upstream sources -------------------------------------------- +# Both trees are gitignored build inputs, not vendored files. Download +# and verify each once, then reuse it across resolves. +fetch_source() { + local url="$1" sha256="$2" dest="$3" name="$4" + [ -d "$dest" ] && return 0 + echo "==> Downloading $name..." + local tarball="$dest.tar.gz" + local staging="$dest.incoming" + curl --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors \ + -fsSL "$url" -o "$tarball" + echo "$sha256 $tarball" | shasum -a 256 -c - + rm -rf "$staging" + mkdir -p "$staging" + tar xzf "$tarball" -C "$staging" --strip-components=1 + rm -f "$tarball" + mv "$staging" "$dest" +} + +fetch_source "$ESPEAK_SOURCE_URL" "$ESPEAK_SOURCE_SHA256" "$SRC_DIR" "espeak-ng $ESPEAK_VERSION" +fetch_source "$PCAUDIO_SOURCE_URL" "$PCAUDIO_SOURCE_SHA256" "$PCAUDIO_SRC_DIR" "pcaudiolib $PCAUDIO_VERSION" + +# --- Locate host LLVM (for glue obj compile + native build) --- +LLVM_PREFIX="${LLVM_PREFIX:-$(brew --prefix llvm 2>/dev/null || echo /opt/homebrew/opt/llvm)}" +LLVM_CLANG="$LLVM_PREFIX/bin/clang" + +# --- Phase 0: kandelo glue objs ---------------------------------------- +# Mirrors mariadb's mariadb-glue-objs/. crt1.o comes from the sysroot; +# the channel_syscall + compiler_rt objects come from the kandelo libc +# glue and are linked into every user program at exec time. +GLUE_OBJ_DIR="$HERE/glue-objs" +GLUE_SRC_DIR="$REPO_ROOT/libc/glue" +mkdir -p "$GLUE_OBJ_DIR" +if [ ! -f "$GLUE_OBJ_DIR/channel_syscall.o" ] || \ + [ "$GLUE_SRC_DIR/channel_syscall.c" -nt "$GLUE_OBJ_DIR/channel_syscall.o" ]; then + echo "==> Compiling kandelo glue objs..." + WASM_COMPILE_FLAGS="--target=wasm32-unknown-unknown -matomics -mbulk-memory -mexception-handling -mllvm -wasm-enable-sjlj -fno-trapping-math --sysroot=$SYSROOT" + # shellcheck disable=SC2086 + "$LLVM_CLANG" $WASM_COMPILE_FLAGS -O2 -c "$GLUE_SRC_DIR/channel_syscall.c" -o "$GLUE_OBJ_DIR/channel_syscall.o" + # shellcheck disable=SC2086 + "$LLVM_CLANG" $WASM_COMPILE_FLAGS -O2 -c "$GLUE_SRC_DIR/compiler_rt.c" -o "$GLUE_OBJ_DIR/compiler_rt.o" +fi + +# --- Phase 1: libpcaudio.a (OSS backend only) -------------------------- +# We don't run pcaudiolib's autotools / libtool — for five files we just +# compile and archive directly. See packages/registry/libxml2/ +# build-libxml2.sh for the same "skip libtool" rationale. +# +# pcaudiolib picks its backend from config.h, the header its autotools +# run generates. Defining only HAVE_SYS_SOUNDCARD_H leaves src/oss.c as +# the one live backend, and it opens /dev/dsp. The alsa, pulseaudio and +# qsa units compile to `return NULL` stubs; they are still built because +# create_audio_device_object in audio.c references their symbols and +# falls through them to the OSS object. No source file is patched. +PCAUDIO_BUILD_DIR="$HERE/pcaudiolib-build" +PCAUDIO_CONFIG_DIR="$PCAUDIO_BUILD_DIR/config" +mkdir -p "$PCAUDIO_CONFIG_DIR" +printf '#define HAVE_SYS_SOUNDCARD_H 1\n' > "$PCAUDIO_CONFIG_DIR/config.h" + +echo "==> Building libpcaudio.a (OSS backend)..." +PCAUDIO_CFLAGS=( + -O2 + -I"$PCAUDIO_CONFIG_DIR" + -I"$PCAUDIO_SRC_DIR/src" + -I"$PCAUDIO_SRC_DIR/src/include" +) +PCAUDIO_OBJS=() +for unit in audio oss alsa pulseaudio qsa; do + wasm32posix-cc "${PCAUDIO_CFLAGS[@]}" -c "$PCAUDIO_SRC_DIR/src/$unit.c" -o "$PCAUDIO_BUILD_DIR/$unit.o" + PCAUDIO_OBJS+=("$PCAUDIO_BUILD_DIR/$unit.o") +done +wasm32posix-ar rcs "$PCAUDIO_BUILD_DIR/libpcaudio.a" "${PCAUDIO_OBJS[@]}" + +# Short-circuit the FetchContent of sonic in upstream cmake/deps.cmake. +# The upstream file unconditionally clones github.com/waywardgeek/sonic +# when find_library doesn't locate libsonic — which it won't on host +# or wasm32 — and that requires network at configure time and pulls +# a stale dep into both builds. We don't use libsonic anyway +# (USE_LIBSONIC=OFF). Replace the whole sonic block with a no-op. +DEPS_CMAKE="$SRC_DIR/cmake/deps.cmake" +DEPS_CMAKE_BACKUP="$DEPS_CMAKE.kandelo.orig" +if [ ! -f "$DEPS_CMAKE_BACKUP" ]; then + cp "$DEPS_CMAKE" "$DEPS_CMAKE_BACKUP" +fi +python3 - "$DEPS_CMAKE_BACKUP" "$DEPS_CMAKE" <<'PYEOF' +import sys, re +src_path, dst_path = sys.argv[1], sys.argv[2] +text = open(src_path).read() +text = re.sub( + r"if \(SONIC_LIB AND SONIC_INC\).*?endif\(\)", + "if (SONIC_LIB AND SONIC_INC)\n set(HAVE_LIBSONIC ON)\nendif()", + text, + count=1, + flags=re.DOTALL, +) +open(dst_path, "w").write(text) +PYEOF + +# Trim the dict list down to ESPEAK_LANG_LIST for the cross build so we +# don't bloat the VFS image with ~80 languages. data.cmake is the upstream +# file we mutate; the change is one find-and-replace and we keep a backup. +DATA_CMAKE="$SRC_DIR/cmake/data.cmake" +DATA_CMAKE_BACKUP="$DATA_CMAKE.kandelo.orig" +if [ ! -f "$DATA_CMAKE_BACKUP" ]; then + cp "$DATA_CMAKE" "$DATA_CMAKE_BACKUP" +fi +echo "==> Restricting data.cmake to languages: $ESPEAK_LANG_LIST" +# Rewrite the _dict_compile_list literal. The upstream definition spans +# many lines; we replace the whole block with a single-line one. +python3 - "$DATA_CMAKE_BACKUP" "$DATA_CMAKE" "$ESPEAK_LANG_LIST" <<'PYEOF' +import sys, re +src_path, dst_path, langs = sys.argv[1], sys.argv[2], sys.argv[3] +text = open(src_path).read() +new_block = "list(APPEND _dict_compile_list " + langs + ")\n" +text = re.sub( + r"list\(APPEND _dict_compile_list[^)]*\)\s*", + new_block, + text, + count=1, + flags=re.DOTALL, +) +open(dst_path, "w").write(text) +PYEOF + +# --- Phase 2: native build of espeak-ng (for data-dir generation) ------ +# The `data` target runs espeak-ng with --compile-intonations / +# --compile-phonemes / --compile= to write the phondata / +# phonindex / phontab / intonations / _dict files. cmake/data.cmake +# always invokes `$`, the binary of the tree +# it runs in, so the cross tree would try to execute a wasm module. +# Build the data here instead, after the two cmake rewrites above so this +# build honours ESPEAK_LANG_LIST too. The outputs are byte tables, not +# code, and both this host and wasm32 are little-endian, so the cross +# build consumes them unchanged. +NATIVE_BUILD_DIR="$HERE/espeak-ng-host-build" +if [ ! -d "$NATIVE_BUILD_DIR/espeak-ng-data" ]; then + echo "==> Native build of espeak-ng (for data tools)..." + mkdir -p "$NATIVE_BUILD_DIR" + # Use the wrapped cc/c++ drivers on PATH, not the bare LLVM binaries + # CMake finds first. Only the wrappers carry the host C++ standard + # library include paths, and speechPlayer is C++. + cmake -S "$SRC_DIR" -B "$NATIVE_BUILD_DIR" \ + -DCMAKE_C_COMPILER=cc \ + -DCMAKE_CXX_COMPILER=c++ \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SHARED_LIBS=OFF \ + -DUSE_MBROLA=OFF \ + -DUSE_LIBSONIC=OFF \ + -DUSE_LIBPCAUDIO=OFF \ + -DCOMPILE_INTONATIONS=ON \ + -DESPEAK_COMPAT=OFF \ + -DENABLE_TESTS=OFF \ + > /dev/null + cmake --build "$NATIVE_BUILD_DIR" --target espeak-ng-bin -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" + cmake --build "$NATIVE_BUILD_DIR" --target data -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" +fi + +# --- Resolve libcxx, then index it into the sysroot -------------------- +# espeak-ng's speechPlayer synthesizer is C++, and upstream builds it +# unconditionally — src/CMakeLists.txt adds the subdirectory without +# testing USE_SPEECHPLAYER. Index the resolved header tree and archives +# into the sysroot the same way build-mariadb.sh does. +LIBCXX_PREFIX="${WASM_POSIX_DEP_LIBCXX_DIR:-}" +if [ -z "$LIBCXX_PREFIX" ]; then + echo "==> Resolving libcxx via cargo xtask build-deps..." + HOST_TARGET="$(rustc -vV | awk '/^host/ {print $2}')" + LIBCXX_PREFIX="$(cd "$REPO_ROOT" && cargo run -p xtask --target "$HOST_TARGET" --quiet -- build-deps --arch=wasm32 resolve libcxx)" +fi +for artifact in lib/libc++.a lib/libc++abi.a include/c++/v1; do + [ -e "$LIBCXX_PREFIX/$artifact" ] || { + echo "ERROR: libcxx resolve missing $artifact at $LIBCXX_PREFIX" >&2 + exit 1 + } +done + +mkdir -p "$SYSROOT/lib" "$SYSROOT/include/c++" +ln -sf "$LIBCXX_PREFIX/lib/libc++.a" "$SYSROOT/lib/libc++.a" +ln -sf "$LIBCXX_PREFIX/lib/libc++abi.a" "$SYSROOT/lib/libc++abi.a" +rm -rf "$SYSROOT/include/c++/v1" +ln -sfn "$LIBCXX_PREFIX/include/c++/v1" "$SYSROOT/include/c++/v1" +echo "==> libcxx resolved at $LIBCXX_PREFIX (symlinked into $SYSROOT)" + +# --- Phase 3: cross build of espeak-ng --------------------------------- +CROSS_BUILD_DIR="$HERE/espeak-ng-cross-build" +mkdir -p "$CROSS_BUILD_DIR" + +echo "==> Cross-compiling espeak-ng for wasm32..." +cmake -S "$SRC_DIR" -B "$CROSS_BUILD_DIR" \ + -DCMAKE_TOOLCHAIN_FILE="$HERE/wasm32-posix-toolchain.cmake" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SHARED_LIBS=OFF \ + -DUSE_MBROLA=OFF \ + -DUSE_LIBSONIC=OFF \ + -DUSE_LIBPCAUDIO=ON \ + -DUSE_KLATT=ON \ + -DUSE_SPEECHPLAYER=ON \ + -DUSE_ASYNC=OFF \ + -DENABLE_TESTS=OFF \ + -DCOMPILE_INTONATIONS=ON \ + -DESPEAK_COMPAT=OFF \ + -DPCAUDIO_LIB="$PCAUDIO_BUILD_DIR/libpcaudio.a" \ + -DPCAUDIO_INC="$PCAUDIO_SRC_DIR/src/include" \ + -DHAVE_LIBPCAUDIO=ON \ + -DHAVE_PTHREAD=OFF + +cmake --build "$CROSS_BUILD_DIR" --target espeak-ng-bin -j"$(sysctl -n hw.ncpu 2>/dev/null || nproc)" + +# --- Phase 4: stage outputs -------------------------------------------- +echo "==> Staging into $INSTALL_DIR..." +mkdir -p "$INSTALL_DIR/bin" "$INSTALL_DIR/share" + +# espeak-ng-bin produces a "espeak-ng" file with no extension; rename +# to .wasm for the package resolver's binary contract. +cp "$CROSS_BUILD_DIR/src/espeak-ng" "$INSTALL_DIR/bin/espeak-ng.wasm" + +# Data dir: the native build wrote it under NATIVE_BUILD_DIR/espeak-ng-data/. +rm -rf "$INSTALL_DIR/share/espeak-ng-data" +cp -R "$NATIVE_BUILD_DIR/espeak-ng-data" "$INSTALL_DIR/share/espeak-ng-data" + +# Restore data.cmake + deps.cmake so the source tree stays clean for next build. +mv "$DATA_CMAKE_BACKUP" "$DATA_CMAKE" +mv "$DEPS_CMAKE_BACKUP" "$DEPS_CMAKE" + +# Pack the data dir into the declared runtime file. Stored-only, sorted, with +# a fixed timestamp and mode, so the archive bytes follow the voice data alone +# and the package cache key stays stable across rebuilds. Same shape as +# cpython's python-runtime.zip. +DATA_ZIP="$INSTALL_DIR/share/espeak-ng-data.zip" +rm -f "$DATA_ZIP" +python3 - "$INSTALL_DIR/share/espeak-ng-data" "$DATA_ZIP" <<'PY' +from pathlib import Path +import stat +import sys +import zipfile + +root = Path(sys.argv[1]) +output = Path(sys.argv[2]) +timestamp = (1980, 1, 1, 0, 0, 0) +with zipfile.ZipFile(output, "w", compression=zipfile.ZIP_STORED, strict_timestamps=True) as archive: + for path in sorted((item for item in root.rglob("*") if item.is_file()), key=lambda item: item.as_posix()): + info = zipfile.ZipInfo(path.relative_to(root).as_posix(), date_time=timestamp) + info.create_system = 3 + info.external_attr = (stat.S_IFREG | 0o644) << 16 + info.compress_type = zipfile.ZIP_STORED + archive.writestr(info, path.read_bytes()) +PY + +# Both filenames exactly match the package.toml [[outputs]] and +# [[runtime_files]] entries; the installer re-checks artifact policy. +source "$REPO_ROOT/scripts/install-local-binary.sh" +install_local_binary espeak-ng "$INSTALL_DIR/bin/espeak-ng.wasm" +install_local_runtime_file espeak-ng "$DATA_ZIP" + +echo "==> Done. Outputs:" +echo " $INSTALL_DIR/bin/espeak-ng.wasm" +echo " $DATA_ZIP" diff --git a/packages/registry/espeak-ng/build.toml b/packages/registry/espeak-ng/build.toml new file mode 100644 index 0000000000..fef7b34bd1 --- /dev/null +++ b/packages/registry/espeak-ng/build.toml @@ -0,0 +1,11 @@ +script_path = "packages/registry/espeak-ng/build-espeak-ng.sh" +inputs = [ + "packages/registry/espeak-ng/build-espeak-ng.sh", + "packages/registry/espeak-ng/wasm32-posix-toolchain.cmake", +] +repo_url = "https://github.com/Automattic/kandelo.git" +commit = "" +revision = 3 + +[binary] +index_url = "https://github.com/Automattic/kandelo/releases/download/binaries-abi-v{abi}/index.toml" diff --git a/packages/registry/espeak-ng/package.toml b/packages/registry/espeak-ng/package.toml new file mode 100644 index 0000000000..44ccd72960 --- /dev/null +++ b/packages/registry/espeak-ng/package.toml @@ -0,0 +1,68 @@ +kind = "program" +name = "espeak-ng" +version = "1.52.0" +# Speech synthesis for the browser demo. espeak-ng links upstream +# pcaudiolib built with only its OSS backend, so playback goes through +# /dev/dsp like every other Kandelo sound port. Neither source tree is +# patched. The build bundles a minimal English-only data dir, published as +# the runtime file below. Consumers install the binary at /usr/bin/espeak-ng +# and unpack the data at /usr/share/espeak-ng-data, matching +# CMAKE_INSTALL_PREFIX=/usr so libespeak-ng's PATH_ESPEAK_DATA resolves. +kernel_abi = 43 +depends_on = ["libcxx@21.1.7"] + +# espeak-ng publishes no source archive as a release asset, so the pin +# is its tag archive. The build script pins pcaudiolib 1.3 separately +# against its published release tarball. +[source] +url = "https://github.com/espeak-ng/espeak-ng/archive/refs/tags/1.52.0.tar.gz" +sha256 = "bb4338102ff3b49a81423da8a1a158b420124b055b60fa76cfb4b18677130a23" +provider = "archive" + +[license] +spdx = "GPL-3.0-or-later" +url = "https://github.com/espeak-ng/espeak-ng/blob/1.52.0/COPYING" + +[build] +script_path = "packages/registry/espeak-ng/build-espeak-ng.sh" + +[[host_tools]] +name = "cmake" +version_constraint = ">=3.15" +probe = { args = ["--version"], version_regex = "cmake version (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "curl" +version_constraint = ">=7.71.0" +probe = { args = ["--version"], version_regex = "curl (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "tar" +version_constraint = ">=1.30" +probe = { args = ["--version"], version_regex = "tar.*?(\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "shasum" +version_constraint = ">=6.0" +probe = { args = ["--version"], version_regex = "(\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "python3" +version_constraint = ">=3.10" +probe = { args = ["--version"], version_regex = "Python (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[outputs]] +name = "espeak-ng" +wasm = "espeak-ng.wasm" + +# The voice data is a separate closure member so image builders and the +# browser demo consume the same immutable bytes through the resolver +# instead of reading the build tree. Stored-only zip, like cpython's. +[[runtime_files]] +artifact = "espeak-ng-data.zip" +guest_path = "/usr/share/espeak-ng/espeak-ng-data.zip" diff --git a/packages/registry/espeak-ng/wasm32-posix-toolchain.cmake b/packages/registry/espeak-ng/wasm32-posix-toolchain.cmake new file mode 100644 index 0000000000..caf05476b7 --- /dev/null +++ b/packages/registry/espeak-ng/wasm32-posix-toolchain.cmake @@ -0,0 +1,134 @@ +# CMake toolchain file for cross-compiling espeak-ng + libpcaudio (with +# the kandelo backend) to wasm32 via the kandelo SDK. +# +# Adapted from packages/registry/mariadb/wasm32-posix-toolchain.cmake. +# espeak-ng doesn't probe nearly as many host features as MariaDB so we +# omit the long HAVE_* override list. + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR wasm32) +set(CMAKE_CROSSCOMPILING TRUE) + +# --- Locate LLVM clang --- +set(_LLVM_SEARCH_PATHS) +if(DEFINED ENV{LLVM_BIN}) + list(APPEND _LLVM_SEARCH_PATHS "$ENV{LLVM_BIN}") +endif() +if(DEFINED ENV{LLVM_PREFIX}) + list(APPEND _LLVM_SEARCH_PATHS "$ENV{LLVM_PREFIX}/bin") +endif() +list(APPEND _LLVM_SEARCH_PATHS + /opt/homebrew/opt/llvm/bin + /usr/local/opt/llvm/bin +) + +find_program(LLVM_CLANG NAMES clang PATHS ${_LLVM_SEARCH_PATHS} NO_DEFAULT_PATH) +if(NOT LLVM_CLANG) + message(FATAL_ERROR + "LLVM clang not found. Searched: ${_LLVM_SEARCH_PATHS}. " + "Set LLVM_BIN (Nix dev shell exports this) or install Homebrew LLVM." + ) +endif() +find_program(LLVM_AR NAMES llvm-ar PATHS ${_LLVM_SEARCH_PATHS} NO_DEFAULT_PATH) +find_program(LLVM_RANLIB NAMES llvm-ranlib PATHS ${_LLVM_SEARCH_PATHS} NO_DEFAULT_PATH) +find_program(LLVM_NM NAMES llvm-nm PATHS ${_LLVM_SEARCH_PATHS} NO_DEFAULT_PATH) + +# --- Sysroot --- +if(NOT WASM_POSIX_SYSROOT) + if(DEFINED ENV{WASM_POSIX_SYSROOT}) + set(WASM_POSIX_SYSROOT "$ENV{WASM_POSIX_SYSROOT}") + else() + get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + get_filename_component(WASM_POSIX_SYSROOT "${_TOOLCHAIN_DIR}/../../../sysroot" ABSOLUTE) + endif() +endif() + +if(NOT EXISTS "${WASM_POSIX_SYSROOT}/lib/libc.a") + message(FATAL_ERROR "Sysroot not found at ${WASM_POSIX_SYSROOT}. Run scripts/build-musl.sh first.") +endif() + +set(CMAKE_SYSROOT "${WASM_POSIX_SYSROOT}") + +# --- Compilers --- +set(CMAKE_C_COMPILER "${LLVM_CLANG}") +set(CMAKE_CXX_COMPILER "${LLVM_CLANG}") +set(CMAKE_AR "${LLVM_AR}" CACHE FILEPATH "Archiver") +set(CMAKE_RANLIB "${LLVM_RANLIB}" CACHE FILEPATH "Ranlib") +set(CMAKE_NM "${LLVM_NM}" CACHE FILEPATH "NM") + +# --- Compiler flags (mirror sdk/src/lib/flags.ts COMPILE_FLAGS) --- +set(WASM32_FLAGS + "--target=wasm32-unknown-unknown" + "-matomics" + "-mbulk-memory" + "-mexception-handling" + "-mllvm" "-wasm-enable-sjlj" + "-fno-trapping-math" + "--sysroot=${WASM_POSIX_SYSROOT}" +) +string(REPLACE ";" " " WASM32_FLAGS_STR "${WASM32_FLAGS}") +set(CMAKE_C_FLAGS_INIT "${WASM32_FLAGS_STR}") +set(CMAKE_CXX_FLAGS_INIT "${WASM32_FLAGS_STR}") + +# --- Linker flags (mirror sdk/src/lib/flags.ts LINK_FLAGS) --- +# Path to the kandelo glue objs that the SDK normally injects. We hand +# them to CMake via CMAKE_EXE_LINKER_FLAGS_INIT so cmake's link rule +# picks them up for `add_executable` targets (espeak-ng-bin). +get_filename_component(_TOOLCHAIN_DIR2 "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) +set(_GLUE_OBJ_DIR "${_TOOLCHAIN_DIR2}/glue-objs") + +set(WASM32_LINK_FLAGS + "-nostdlib" + "-Wl,--entry=_start" + "-Wl,--export=_start" + "-Wl,--export=__heap_base" + "-Wl,--import-memory" + "-Wl,--shared-memory" + "-Wl,--max-memory=1073741824" + "-Wl,--allow-undefined" + "-Wl,--global-base=1114112" + "-Wl,--table-base=3" + "-Wl,--export-table" + "-Wl,--growable-table" + "-Wl,--export=__wasm_init_tls" + "-Wl,--export=__tls_base" + "-Wl,--export=__tls_size" + "-Wl,--export=__tls_align" + "-Wl,--export=__stack_pointer" + "-Wl,--export=__wasm_thread_init" + "-Wl,-z,stack-size=1048576" +) +string(REPLACE ";" " " WASM32_LINK_FLAGS_STR "${WASM32_LINK_FLAGS}") + +set(CMAKE_EXE_LINKER_FLAGS_INIT + "${WASM32_LINK_FLAGS_STR} ${WASM_POSIX_SYSROOT}/lib/crt1.o ${_GLUE_OBJ_DIR}/channel_syscall.o ${_GLUE_OBJ_DIR}/compiler_rt.o -lc" +) + +# --- Type sizes for wasm32 ILP32 --- +set(CMAKE_SIZEOF_VOID_P 4) +set(CMAKE_C_SIZEOF_DATA_PTR 4) +set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + +# --- Search paths --- +set(CMAKE_FIND_ROOT_PATH "${WASM_POSIX_SYSROOT}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# Disable try_run; espeak-ng's check_symbol_exists / check_include_file +# only need to compile, not link. +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +# espeak-ng's USE_ASYNC option gates on find_package(Threads). Kandelo +# libc provides pthread but CMake's Threads detection runs a try_compile +# that may falsely conclude pthreads is missing under cross-compile. We +# advertise it explicitly. +set(THREADS_PTHREAD_ARG "0" CACHE STRING "" FORCE) +set(CMAKE_THREAD_LIBS_INIT "-lpthread" CACHE STRING "" FORCE) +set(CMAKE_HAVE_THREADS_LIBRARY 1 CACHE BOOL "" FORCE) +set(CMAKE_USE_WIN32_THREADS_INIT 0 CACHE BOOL "" FORCE) +set(CMAKE_USE_PTHREADS_INIT 1 CACHE BOOL "" FORCE) +set(THREADS_FOUND TRUE CACHE BOOL "" FORCE) diff --git a/packages/registry/evdev-demo/build-evdev-demo.sh b/packages/registry/evdev-demo/build-evdev-demo.sh new file mode 100755 index 0000000000..31e017e973 --- /dev/null +++ b/packages/registry/evdev-demo/build-evdev-demo.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$HERE/../../.." && pwd)" +# shellcheck source=/dev/null +source "$REPO_ROOT/scripts/package-build-roots.sh" +kandelo_package_prepare_build_roots "$HERE" wasm32 +kandelo_package_select_source_root "$REPO_ROOT" +SOURCE_ROOT="$KANDELO_PACKAGE_SOURCE_ROOT" +EVDEV_DEMO_SOURCE="$SOURCE_ROOT/programs/evdev_demo.c" +WORK_DIR="$KANDELO_PACKAGE_WORK_DIR" +OUT_BIN="$WORK_DIR/evdev_demo.wasm" + +if [ ! -f "$EVDEV_DEMO_SOURCE" ] || [ -L "$EVDEV_DEMO_SOURCE" ]; then + echo "ERROR: evdev_demo source must be a regular file: $EVDEV_DEMO_SOURCE" >&2 + exit 1 +fi + +# A resolver/Formula caller owns the declared work and output roots. Keep the +# reviewed checkout read-only and suppress the developer-only local mirror. +if [ -n "${WASM_POSIX_DEP_WORK_DIR:-}" ] && [ -n "${WASM_POSIX_DEP_OUT_DIR:-}" ]; then + export WASM_POSIX_INSTALL_LOCAL_MIRROR=0 + export WASM_POSIX_INSTALL_FORK_INSTRUMENTATION=auto +fi + +source "$REPO_ROOT/sdk/activate.sh" +export WASM_POSIX_SYSROOT="$REPO_ROOT/sysroot" + +if [ ! -f "$WASM_POSIX_SYSROOT/include/linux/input.h" ]; then + echo "ERROR: the vendored evdev headers are missing from the sysroot." >&2 + echo "Run: scripts/dev-shell.sh bash scripts/build-musl.sh" >&2 + exit 1 +fi + +echo "==> Building evdev_demo..." +wasm32posix-cc \ + -std=c11 \ + -O2 \ + -Wall \ + -Wextra \ + -Wno-unused-parameter \ + -D_DEFAULT_SOURCE \ + "$EVDEV_DEMO_SOURCE" \ + -o "$OUT_BIN" + +cd "$REPO_ROOT" +source "$REPO_ROOT/scripts/install-local-binary.sh" +install_local_binary evdev-demo "$OUT_BIN" evdev_demo.wasm diff --git a/packages/registry/evdev-demo/build.toml b/packages/registry/evdev-demo/build.toml new file mode 100644 index 0000000000..b10cef4be5 --- /dev/null +++ b/packages/registry/evdev-demo/build.toml @@ -0,0 +1,15 @@ +script_path = "packages/registry/evdev-demo/build-evdev-demo.sh" +inputs = [ + "packages/registry/evdev-demo/build-evdev-demo.sh", + "programs/evdev_demo.c", + "libc/musl-overlay/include/linux/input.h", + "libc/musl-overlay/include/linux/input-event-codes.h", + "scripts/build-musl.sh", + "scripts/package-build-roots.sh", +] +repo_url = "https://github.com/Automattic/kandelo.git" +commit = "" +revision = 1 + +[binary] +index_url = "https://github.com/Automattic/kandelo/releases/download/binaries-abi-v{abi}/index.toml" diff --git a/packages/registry/evdev-demo/package.toml b/packages/registry/evdev-demo/package.toml new file mode 100644 index 0000000000..87994ad491 --- /dev/null +++ b/packages/registry/evdev-demo/package.toml @@ -0,0 +1,27 @@ +kind = "program" +# Runtime backing for `/?demo=evdev`. The browser app imports the output +# through `@binaries`, so it needs a registry owner like every other +# product artifact — see scripts/browser-binary-package-roots.mjs. The +# source stays in programs/ so scripts/build-programs.sh keeps building +# the local test fixture, the same split modeset uses. +name = "evdev-demo" +version = "0.1.0" +kernel_abi = 43 +depends_on = [] +arches = ["wasm32"] + +[source] +url = "https://github.com/Automattic/kandelo" +sha256 = "0000000000000000000000000000000000000000000000000000000000000000" +provider = "repository" + +[license] +spdx = "GPL-2.0-or-later" +url = "https://github.com/Automattic/kandelo/blob/main/COPYING" + +[build] +script_path = "packages/registry/evdev-demo/build-evdev-demo.sh" + +[[outputs]] +name = "evdev_demo" +wasm = "evdev_demo.wasm" diff --git a/packages/registry/program-packages.json b/packages/registry/program-packages.json index 8f683eecc6..5095fdebff 100644 --- a/packages/registry/program-packages.json +++ b/packages/registry/program-packages.json @@ -4,344 +4,358 @@ "bash": { "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", "cacheKeys": { - "wasm32": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54", - "wasm64": "3513602ddc7d88d11e8e04bc9e7dcd569862b96270b549e76d179b19180a2586" + "wasm32": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269", + "wasm64": "892c9c836c1718d2aa49ddff0071eedd843526cdf4f4276f8a82f275e80785b6" } }, "bc": { "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", "cacheKeys": { - "wasm32": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d", - "wasm64": "e1cdba537940df33cfd9fecccc0a0eeeb85032e4708d61e993b15d3b8efb755b" + "wasm32": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6", + "wasm64": "ed5ee5fb9d55fff4586c1cbaf68842feb48f2d00a038bb66f78267dfee29e2b1" } }, "bzip2": { "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", "cacheKeys": { - "wasm32": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778", - "wasm64": "9678856dff9a5794e2c696370100089065b6d6e8f0447ca00100a56ac6729618" + "wasm32": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86", + "wasm64": "1f6e7f57356f20484fb05f4488664e0a3cbdb10ecdc7e4de58bfd5b7ff5bd282" } }, "coreutils": { "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", "cacheKeys": { - "wasm32": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24", - "wasm64": "ed081404db705f2d4dcf3a1e5319c4a22809dff9b84b9fd0bf4b3ae3f3913e3b" + "wasm32": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96", + "wasm64": "718d3028065caf74d3e9d6f4563133690dcd44eeb12dfc20e2efe790315770c4" } }, "cpython": { "manifestSha256": "3e5bd98bf8221902ca214d73117fa71d7dfeb0c75cf5c1fe219b82eb94bb9ff0", "cacheKeys": { - "wasm32": "671600d839333cd841b35d44f182836e9d9f515b0da5f13ece812b15c99aa797", - "wasm64": "43d0892454e97970a0e6db0be8be9cd987942d8926fcf8633978e7ad1b635a4c" + "wasm32": "24754c020c2a08b5cf2502ea4f3010c53c1c0b4c2094fa5d597221d36afc29b1", + "wasm64": "2439e021e1195a424e61d6d477dd843501a697e914d12dd07ee108e314c2056a" } }, "curl": { "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", "cacheKeys": { - "wasm32": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668", - "wasm64": "95dedeacb60f00877b225f2521e1edb4a4ed7a231ffaf3084e456d1b3a303651" + "wasm32": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7", + "wasm64": "35d33e823da444eaf1cbd02e4408b0b7b9814d747afea707c309834a5d4834ac" } }, "dash": { "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", "cacheKeys": { - "wasm32": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0", - "wasm64": "8a658122b3dbef11e1a4cdbff2b56b9756cde0e4c3942c3398cfa01046da3cc0" + "wasm32": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf", + "wasm64": "bf94c26f54f24404557058c49c772d0f25cab107d0b1a3a8a937fb6fdb71713a" } }, "diffutils": { "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", "cacheKeys": { - "wasm32": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985", - "wasm64": "2275c153bf3aac6cdf80f64ec498b9f54c08711c3022af8b5fbd002c68927242" + "wasm32": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e", + "wasm64": "df1994778c95ddde2eeec8a0b6cf0503a9e5310377158b5ea775cff30155eca1" } }, "dinit": { "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", "cacheKeys": { - "wasm32": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf", - "wasm64": "cf9024c623d71b848048b4b68ace29ed182fe173df89b56ed02177018a557ce7" + "wasm32": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255", + "wasm64": "986479c451806fa661c3a1773f3aea1d1390603e1fe547c661d298fb2d1df1e7" } }, "erlang": { "manifestSha256": "475d6037e73a0f1e2f4381067194b2422751206979ea17209e10efa5a2a93bbb", "cacheKeys": { - "wasm32": "54c816a671fdaa12a5ab18580c4e54d423f546c56cb5a237449f090c68cda9d8", - "wasm64": "a70ae3d720d9f827bd21f4fd9f378fb982a1ec09cbb90db1a764ec8e9ff63c52" + "wasm32": "a84f5187db542cc4a7f68d9747a5e9096e610a261335e629e785eb4b798f79b3", + "wasm64": "0fc3b6b7683689375872052d3a10018bf33b57df2019b60bb7a5e265a5b2817d" } }, "erlang-vfs": { "manifestSha256": "15efb74f1825e2b85bedfeb8d98c19fa648c4be39cb9defb65330a8f21eb9141", "cacheKeys": { - "wasm32": "0d2025175cc7675a3d69bef53ffb084b8b2bb719fca8f437147ca36341c28014", - "wasm64": "f96824ee4e1f6acc3dceabf45b6514c76050e283488246fbfc36a8db92cdf496" + "wasm32": "a959310a47cb3b6e483ce0737ea31ca4c3cef6361e40b9ec28c2cfc38efa5f52", + "wasm64": "03b03659f6a56b3ddb7ad6aa5e771812fe22497e3d53a9792d165c29641a3674" + } + }, + "espeak-ng": { + "manifestSha256": "ae6c7b301617b5541bbf9fb9ff68a886f05c6e19f5393eacf03b50172ae5477d", + "cacheKeys": { + "wasm32": "0c3735cdb7871c3782a3494617163b6c5294db3d8afe6aaa1a6b7ec56b2e2d4a", + "wasm64": "9af3ba96ec7382e8f4875c64dfad3eeccbe7759cd580ad2999049fba0f68269e" + } + }, + "evdev-demo": { + "manifestSha256": "79e367095cbe154238d3b54b472fdea8426e4aa9d3a96afce61b04f656126952", + "cacheKeys": { + "wasm32": "0cb7324a23a8380757d5e5921e6f73f4c55ccd983f72d129d3ea4567adbf5ffe", + "wasm64": "b444e77dd10593ef070b956dfc6daad73ebef8c77d8c06f5611e04b8e9c7643a" } }, "fbdoom": { "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", "cacheKeys": { - "wasm32": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229", - "wasm64": "116b1d9e44e323c42819e0ebf5bedae5588637fd0e5229ff2803f9a522405c55" + "wasm32": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80", + "wasm64": "827e782f7568de46df5b3cb0bb1a8c6d77d8f747dda6af0a77b8875a3cfad180" } }, "file": { "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", "cacheKeys": { - "wasm32": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1", - "wasm64": "aac8bd9b1687c450bf74cfa0f8325b53e155346f4a101ddb4faafadae7ad812d" + "wasm32": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8", + "wasm64": "1d6f508aef4603f51e6d80801569b64e1ff53365ad99f49b36afb9b84d1073e8" } }, "findutils": { "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", "cacheKeys": { - "wasm32": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c", - "wasm64": "78eb4c3e829ef7698fa678014a5c5d758713df463ccc376e9e08aba8f3884ae8" + "wasm32": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906", + "wasm64": "c39d2c2a21e320d61335e1c02b2e466378d31939e966578a71438a98c8d87239" } }, "gawk": { "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", "cacheKeys": { - "wasm32": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d", - "wasm64": "a12fa4d7e51f6692791c998d078b74b3cb58891892f2f3c9fc05328d0378c79f" + "wasm32": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072", + "wasm64": "8221ab5a612e051733a46d3cade389b6758f251ade60a349837d0d26e76c78ba" } }, "git": { "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", "cacheKeys": { - "wasm32": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf", - "wasm64": "e516189d137bbeda0df157bed40eae356901d74acf0583601ae1d42f6739297b" + "wasm32": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1", + "wasm64": "e9b2cd5ffa0699c8f0657cf4e4078703c5f4d541c164c0c2670b58cb2511a784" } }, "grep": { "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", "cacheKeys": { - "wasm32": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc", - "wasm64": "7fd4dc51aa8820505934fe68bac99a276836be52c4c26b588febba956a09964e" + "wasm32": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647", + "wasm64": "8dfdc9b0c715992f75c9dfacf981812124915924f45dbcc81902ce6f88dbbe5f" } }, "gzip": { "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", "cacheKeys": { - "wasm32": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544", - "wasm64": "c2af1b8ca56a9bfae55ee3a033e928e3142313868801cd3eadf2ada21e5f0fe5" + "wasm32": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7", + "wasm64": "d2c66c3f3850e34fb0b5a68a3a0b7245b781a6b2e3d19989339c1da7493c9034" } }, "homebrew-bootstrap": { "manifestSha256": "c4ed5ba78c33909ac70410c86a80bb877f86fa6ac786c8f11025410a8df1fd24", "cacheKeys": { - "wasm32": "bf2afee293e5c1c5520beb9311d410b81be3140134d414f51c3580dd5c672fa4", - "wasm64": "1fca4e3a69ea4dbb7c6843ec75db428dae9b3983813c831d890fef56d3805ee0" + "wasm32": "91b465b6881947705159383ae7f518d0877628f55f6a54ae201f9d764c2315e4", + "wasm64": "bbb898a8b0c53323012e57b9099f09cad31fde4f78f16aed6ceb18e26c7a60a9" } }, "icu": { "manifestSha256": "e1169851c1978d3356c4e4c0f396672cb6b7785375a44411f7211ddfc90e7ad5", "cacheKeys": { - "wasm32": "29b737564e6d227336ef5ab8853ea2a2bf18b6d01f4619e04252cb9a66aea401", - "wasm64": "cdb369e0272499a0fee60eb852de2cc8b5fd16d1472f5c486979bff289fb09b1" + "wasm32": "d3da6e8512e1edd9452bf2f7898756037a1074bbedf191912c6bbbb827ef245a", + "wasm64": "0025c9e9883214f94f520c7f148cb9f58da9bd29543495a7318c1cd08dfdeb23" } }, "kandelo-sdk": { "manifestSha256": "82057bff05e5e819b26c7ea4457cff198cadf7075b6b02aeb22014811e677abe", "cacheKeys": { - "wasm32": "325158d5bb1f323c3f99650703e0123c26dda5abbf2bf6c2bfc814ae6a70d304", - "wasm64": "4e617a34764c4036907a071345104f5a1026299c7bdd7230b4b92a59afe4d942" + "wasm32": "1b32cb3488a50b98494858b057e82eb29f53cbce1d27d40480bb1559158a5735", + "wasm64": "07be0f2dea0cadbc56a830f14b8271b171daad881aa71a9e83a21e2cb2630807" } }, "kernel": { "manifestSha256": "3cfdfa2130a0917b23ee9ae760901e5caa225654169a51f767413c38025b7b2d", "cacheKeys": { - "wasm32": "6f7364081f0971d3948eaf7c3f8b11b00b0ce98bc9d1525c75d03ef056f65d71", - "wasm64": "72d2fcb1844d95f0ecf60b907d83c356cd0ef300ba36baf9fbf2fc1383defd93" + "wasm32": "db62f6bd1409ee6c7f465f21d796dc4717d63fa2bed8bb2c6e7d86cbc4c5b217", + "wasm64": "24138379abc03991f7318688304862270df09b19366b4440186874ca33b6a47b" } }, "lamp": { "manifestSha256": "3aafffb710a15cd75d974d1cd918c2fe9966c3809144fc46c6057e3ed6c89ae3", "cacheKeys": { - "wasm32": "d206b4754b798d0d45e5f2c8c3a7d4f6f08eececd660ad8cf484d6ddcac1e0e2", - "wasm64": "8e990ba70d1fb887c1c3f8c57748bed9802078ac95737250452f1035ee596761" + "wasm32": "5c9dc3d7f0101fbcb958ce8517938789071796d02de39c05730effe81437318b", + "wasm64": "09699ebedd9ef27729c268ec296be98f283556b2379e932ec00612db9da16821" } }, "less": { "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", "cacheKeys": { - "wasm32": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8", - "wasm64": "083f4c4cbd7282faf93ca840182195ccb4ed0c764c0b2e0baf0dd21d283f1511" + "wasm32": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989", + "wasm64": "fbab51fbeb01039e6a812ffb7874fa02debaf5eb815ba9f533c6fe8bd613ff43" } }, "libcurl": { "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", "cacheKeys": { - "wasm32": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1", - "wasm64": "7ece61a1cbbe02608a6894c3c4ef2367b0c374d852d6e0f9abac8ee3223e8f14" + "wasm32": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4", + "wasm64": "5291543971eb2a6a82851eb6b4a05c94c97568353ad9b4fcc9480a772c57e46f" } }, "libcxx": { "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", "cacheKeys": { - "wasm32": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66", - "wasm64": "5fed4cef387e8aa1dba438ab123b54e9324dc7b33a448a31d203f78df73b1d02" + "wasm32": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a", + "wasm64": "67b0dd58954a0d9f9433bcdb5d5064f3575bd48ba84dd7ca7ff1400ea0181467" } }, "libiconv": { "manifestSha256": "7ae4f68412db04f01e027f94b0d14c1130ac4dfb2041270207994ddab56f0855", "cacheKeys": { - "wasm32": "939a64271c7d51277a5ec7a49c45c55fb4c4b252c68361ca3c6beb6bf9a3111b", - "wasm64": "71d34d650ee8d46d24f5a9d9bc807392b66a1669397a60660ab128c68fe57123" + "wasm32": "2bc2df82f5537c5f74f7e06dc3e6375f4ef24a0866e54df0f2e75f0152baf538", + "wasm64": "0023ade8bddf07af5e939b03ca76e9b8286f6fb7d4c261861a5366f18e5db4fa" } }, "libpng": { "manifestSha256": "d21c80ef4dedf35828c1cc74d741abdafc003f7048cf474b2fdfd9143016bd76", "cacheKeys": { - "wasm32": "7b652e83e5ea6e2619e01d2cc13415258f5b87013bbf7e373a72b848b6b5bec9", - "wasm64": "6fc690ea5fe2c08ff26104c92635e5cd614976593ecbaa4c2a782d41b8393399" + "wasm32": "42ca6a45c189e22774e064cefc799d7790a9dc83ab06ccd41a6b7594e9ff8e93", + "wasm64": "eb32a005c81c8fa7ff1828aff897ae54551ac6ec6e78d914596fff513bf9541d" } }, "libxml2": { "manifestSha256": "87301bfcb607ee24c00925787cfe071ba506c750437ff5b807b2ea0897058e91", "cacheKeys": { - "wasm32": "0f93f0ef28cebb24d87516d0db6683a8909359a10f1577db92a8374b04ae7cdd", - "wasm64": "18314ef7d2eaf6c112c27a7ba6195626a7fe67cf946d670c8375bfc27b2d408a" + "wasm32": "a90b8526a47a799b2917d4dc3c6d16f1d65ece66005e484a322c503e5c5805d8", + "wasm64": "162fd040ad754386e0d172835b8843e7ca3d6efc39144ce6d07e16abdbb79759" } }, "libzip": { "manifestSha256": "84a6e61fc67c7dd6cf5b4aacd80682a86d402c78810efdd03a9ff58da8eaf6a0", "cacheKeys": { - "wasm32": "f7b217af4f83eeb207d7cbfca570144719a67f4df6ae3c1fc56a8694dd0568ce", - "wasm64": "c39dcd026786435afeb70ca8cd1a8ece4e99af1e9ca73150d55fbfbc67bea46d" + "wasm32": "7ddffc7b50eea7a8ecebc94f88c1f6e6953b1d1bfd0bb30bb6f33805c3f100da", + "wasm64": "918b4ed985861add1ac86218955f8b5178c7a96e8547dab61d5e49a1091a5ff0" } }, "lsof": { "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", "cacheKeys": { - "wasm32": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8", - "wasm64": "c23d85540e06702588e7bc94ad2bace5ea6d0242a3c70278e57128eeaa19cd2a" + "wasm32": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760", + "wasm64": "1f25079f00d5ea3bf259d4258bed06f34508a95711e81335773db1a6cb3683c4" } }, "m4": { "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", "cacheKeys": { - "wasm32": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695", - "wasm64": "312bd294fa500808e76e79d5ac3d0cdbc920bccb44e67a3f30132e66eaedc95d" + "wasm32": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d", + "wasm64": "a2f64accb5325fca10d10cb009d7943a6263394ba05b2076ed94eff11e7db87f" } }, "make": { "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", "cacheKeys": { - "wasm32": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5", - "wasm64": "7f57a202f65c5c1ace8e35c1fe083f6208861d98c7705ea23155a6794ee62148" + "wasm32": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb", + "wasm64": "5607e274689479929e9e9c665034a1a43961e2761c4f0b133b948f5d0427e3a5" } }, "mariadb": { "manifestSha256": "2e0699de7658d2ae002556c84f49ccb61a70e96b500b41c5e73246570d921bea", "cacheKeys": { - "wasm32": "48bc77b348e61faa8b0e7430fa8cc02cf0136548cfd6b342bb3d7dfccf9c138b", - "wasm64": "7ecdf7257a8395280927964eecda07d31a6eacd1f09f9a0775a5ee29876f87db" + "wasm32": "11a884f2ab079b77a5476dca2475c0ab8f693c0fd9078a9218fcd03f81e1c4d0", + "wasm64": "f1dd516fed2d03f9699d77d69b5abcece8ebfe03628cc208f8d0d1c5116608f2" } }, "mariadb-test": { "manifestSha256": "e88dffcf866ca8ed351b2601ae3645986fdbd1761b19b4d92d17c6d4eae471e0", "cacheKeys": { - "wasm32": "dca8b89a45f43c9ed3b079d8a5505d4870724ffeb84e20f404668a631f1a0263", - "wasm64": "1ca2525de6ae90e4f0673decd4b28f50f8a4aeadd7dcecac56a0e107e4277443" + "wasm32": "c416deee68dc98a54c46f762ff9ba69363cb77f647bc3093855494f022f21936", + "wasm64": "bc9427d2b837d7e7b7d739033950f23ebc12997e158b9537012f530281585c1b" } }, "mariadb-vfs": { "manifestSha256": "26e74ac84d89b2a839ed72783437061a23022ef2f88ad0f52b6aacd0442ee1cf", "cacheKeys": { - "wasm32": "a41210be1a20284debb0b47837090c26aefda421515ba67c126fc9b1f9604ff7", - "wasm64": "000555b6d6f44d64e84211d2963cc30e733beff57ceebd0dc04444e5cfb034e9" + "wasm32": "38422a41bba8901715bd540b312b7eb3cf6381eb55bd5640524ea3bbbea10367", + "wasm64": "3858257711ebd3f6250a2085c03e69662ae95e44abb6d906400b908d5094e92b" } }, "modeset": { "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", "cacheKeys": { - "wasm32": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60", - "wasm64": "c60c077cadd1ec3561250e505f45bb0aad3ed0026cd8578a219e830da7352e1d" + "wasm32": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0", + "wasm64": "cba500205a355f7d848f6faeceb5b25fbcc0b32070ad05d49666fef2391be6ac" } }, "msmtpd": { "manifestSha256": "eb554efa7f11b5d2c2268492eecf35bc0d80eeebbcffc1441b590b5541b4ec16", "cacheKeys": { - "wasm32": "9a013fcbb76a91f84bcf10962e77fb9f79ab66ad8ab9cc066d1053a92aa6a4a7", - "wasm64": "27a056a9efe257f65caa72f021d226b86c257d7496faad824afcba1dacc7d34d" + "wasm32": "7c8c1a37ebb098b5648349ac9d1600b212ace546c6d8ef87258e6796d78e0be1", + "wasm64": "aca10e8da3a8e7dbb822480f61f09bf1c33f879fed479e83edf9bb69ec3d9b53" } }, "nano": { "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", "cacheKeys": { - "wasm32": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086", - "wasm64": "37d3481fa0bf7efc7bd97ff3f379e6210bc19796c482e28955d8a174d8a34646" + "wasm32": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce", + "wasm64": "a4bc6044a870801efada6c2a9e362f634531b8f3571499abf1a3c5040a0baf3f" } }, "ncurses": { "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", "cacheKeys": { - "wasm32": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97", - "wasm64": "291382c9ade0dbf66dbc47710038693a8069803e06d95cfd93eab1e6f50cfe62" + "wasm32": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4", + "wasm64": "773055d116ec398bd852fdb89d557d5a296c5cec434788f355a4bed624d315f8" } }, "netcat": { "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", "cacheKeys": { - "wasm32": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28", - "wasm64": "b6f9e093570515f50625f9270097bae855db48d6839b2efa9c9bd0e8aaed6906" + "wasm32": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a", + "wasm64": "415a72e2ef1148f41db8f3dcefb8064ca2aa828e1c4354098a115997889b8b92" } }, "nethack": { "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", "cacheKeys": { - "wasm32": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d", - "wasm64": "6fa7f33044b08db08fd376da60c44dacc7a49f2ee3e2b25301be152a15f800dc" + "wasm32": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12", + "wasm64": "d36b9a3ce47de215080934fd61c53fcb65eebcdffa28b32da8423dbb1a3fa6d8" } }, "nethack-browser-bundle": { "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", "cacheKeys": { - "wasm32": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487", - "wasm64": "de395aacbc3558d9539ae5d248d1ba69baa23f36ad2ad2c4547557fe3629c16b" + "wasm32": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6", + "wasm64": "136ec10be0ba7a3a383da37739ec34c3fb539ee6ab40fe8345db63320a348812" } }, "nginx": { "manifestSha256": "b78552502c63bef2814c8aae76fedfdb4f273e4ee0c42023d6157ddb298ff815", "cacheKeys": { - "wasm32": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640", - "wasm64": "fd39a47be95708eb12f5f8a7729dda000d50e319d01d28535ccf5cc80be7a7bd" + "wasm32": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a", + "wasm64": "8f76827a72b21bf7285092e575b3ab15fbc220b6afb5e0c9d59eef46e290f505" } }, "nginx-php-vfs": { "manifestSha256": "39e6795e7824a252715a0e8dde34d0ddbacd42757a603f2263e50a278ea114d5", "cacheKeys": { - "wasm32": "e90acec39405a648ced9a361bc1008b198322d5d2abb40b32aaaecff59379b09", - "wasm64": "9329c97cf06450c15200c7dd9dbaf7086698342f072900152f536c9044bf563a" + "wasm32": "e4462832b24f124b23314eb26add749ca515fe1a38f78834816c0fae0c49da82", + "wasm64": "ab41af32f0ba8b41e7d752ccf7b7a7012b4ee4083efec0c0422485282e85ec48" } }, "nginx-vfs": { "manifestSha256": "66c8f01601e7cf0d826fbd22b641a3d7f7d050bb0980a69efbce73d2045d9866", "cacheKeys": { - "wasm32": "e9a37566343366381720bd5d48cb5bffd3b74c1164901ce8e20970279332948c", - "wasm64": "bb8f67d990eaa15fe01bfa32fbe447181519666ee70f659a37d514bb03f90874" + "wasm32": "d3df523ac43da00f2dde4c938389e7f6bff55c86bf582a8300b519ee412e23e3", + "wasm64": "5f17052e400710b4c22b8d79e01a927efb6523e940af80ae1694383b851cf56f" } }, "node": { "manifestSha256": "353bce1b61a21b2132340a9f6f1e2d7e9354a592b3d9762165a215ad390c23c2", "cacheKeys": { - "wasm32": "010787fcdad88941f9bb4fda4d3b19b3c479de269ffd5d47588e25bdf375ab1e", - "wasm64": "b316c070c4f7577afe4e8683386ed9c13a19f3c7ab692d9f786eef694f7c522b" + "wasm32": "d090c5dffda26124b0fd2f6c439e9346fd336ab65e5e98c27f734fc30627a0f2", + "wasm64": "c589a4d09b2003260552107f30e8965a1c77b52b1ac74981b56c5b45fa92bdc7" } }, "node-vfs": { "manifestSha256": "44f46b62e2cf5211cf383e639b62e9841b9b8ba8f5b06f5c098a8e319b3d79aa", "cacheKeys": { - "wasm32": "bdcaae9a5dc421e4a4fbd7cebac8b628c585923f75562ca0917e176c39b36aa9", - "wasm64": "9e4032f5ccacc668096cfdb7afc4be263db4fcc2c05af7f64d3ecf5b3332aad4" + "wasm32": "7a6c0893e1404bcf91813971d297e87afcd96363308731e1364bce5c4458bfdb", + "wasm64": "20192b4a4683b3ba0a452a2eac89b26bbc982b2f132824686c5c6327f342aa10" } }, "openssl": { "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", "cacheKeys": { - "wasm32": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246", - "wasm64": "5b8a8e6d882753b092233ecb65fe37add307d8ef9bff73e53d0ea4a1f06bc690" + "wasm32": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec", + "wasm64": "926ecabe79fbf960e9ff74074298c1c298a98dbc1a29a1adee52d9f346130f75" } }, "pcre2-source": { @@ -354,197 +368,197 @@ "perl": { "manifestSha256": "dfebfffe4136be5b38e5f7aef14d75068e42ac9a05541582d005a6992f41c216", "cacheKeys": { - "wasm32": "e4ec3cdc5b7ceacb9b9be1d93ff3c52247e95d8ec3e07927b6c3fb059909740f", - "wasm64": "3db5669812995a6ae9c8bba0ac262b72eba868594b6a5cd41d99c9032e7b2d6d" + "wasm32": "a96df7fab0a5a61a0541f75698b2a963ee80732d6efad7dc57a6094c80d0a7d7", + "wasm64": "8b0bb019b6d46eb4e6661069615bceeeb2f972845348cc3466dbe698f0f4fb14" } }, "perl-vfs": { "manifestSha256": "1345cc102bc8c24a6bc276410c00b4fcc99ba5f6adc9b031f882aaa35d53c8bc", "cacheKeys": { - "wasm32": "a8a53f1dfe72eec9bfd6c6867aa9c0babd6fc2783988747109173ba605bab1e1", - "wasm64": "7dd8cb1b1e9d3a2e94f3bfb7567bea31a5efd04c577af4ddfa2b719e2454ee08" + "wasm32": "7a52dff193297d1d438d032687fad3debbb98ed6b70ac9a6f56547d86c65ee5e", + "wasm64": "b1db07c79fde2643c5aa5b2c7a01b7c3c73d395413c0a7d96bd52b52bfa21ced" } }, "php": { "manifestSha256": "14114e280941f1f2f6a4c5abc11698b4b01f909be4cf2aac5b0e68845fed4644", "cacheKeys": { - "wasm32": "6062638f2f20a7254948ee6bede9c4a1515d0ff4565abb15084b037c3510ab05", - "wasm64": "40aabe41edeaa6f8c3b29a7129a5b06e44ac14584254c9961a873e4ef5e23c0b" + "wasm32": "70a85fce082656d22b33c356285b2659a00ff59e3efeda9f35862e8fdd6c0abd", + "wasm64": "5119fe080a78e0c899d60922142753c5ed19bf27d1e1ecd31cad5685b5fff762" } }, "posix-utils-lite": { "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", "cacheKeys": { - "wasm32": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9", - "wasm64": "afff63e0cf32abebccbf6f1d1b3f19eeca013c4d325d05654d490f047b5b8eb8" + "wasm32": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2", + "wasm64": "edd6ab0985d46fec372b2222281c49281a2731b21d9f13071179f3294473ed5f" } }, "python-vfs": { "manifestSha256": "d1aa99feae65f06c0ca8cf52c2176534b2723fd4ee6eba6e72c205245e1c9c26", "cacheKeys": { - "wasm32": "d99fdd4b6bac0a13639e1e1a99bcd10a5122f4f2fa86d908c9a6267219bc7ee1", - "wasm64": "2e757bc143010ef2bdb44df0ec8c24bf5f770a762519f9a9ef1af2eaa505e890" + "wasm32": "79403e28bbf504b073ee6c4bcadffe3344e2004454eb5d906118b11e6ade602c", + "wasm64": "06767b946c57f8ad36406ce6454a0a8d48890599870a0116b55c43e76e38cb68" } }, "redis": { "manifestSha256": "94bfd24dd43bea67bb036fa52e8ab9a88ab5c29d9597e2a7e32b30cac9474d42", "cacheKeys": { - "wasm32": "4a90606ee4c37280f794d9b6249b80f8533c76a9148ee86ebc0ca609d437ba05", - "wasm64": "62427ccb76e52b58c1928578bf7b5f62cc7e526066ac69e31c53ae0a500affdb" + "wasm32": "c6d0e0623fd00ec516ed08d36771c34cc0c76019bf3a65bda82371e0d49e3239", + "wasm64": "6125646975e74e2be27575300b36e5eea0e5c05832890ae77f0f88c7b4f1d2c6" } }, "redis-vfs": { "manifestSha256": "234c45c40f94e2a89f98295313b82cb9fce15a412ac829d9e87394e0dc731813", "cacheKeys": { - "wasm32": "13a85bd7b4cfadbebf9d2aa3966988dac7e37aea7897453a6eaa4bc8e9f9ab0d", - "wasm64": "293bf84ec790e413df07047d84fe1099aa389e1a8a057a1d4bdc8a062987c4e0" + "wasm32": "bc088fa32d51ca9ad14533e12f66ed674cb3fc9e92057086b6ccf6ed214a7462", + "wasm64": "a4d0d6aa1d306138eb701953ae4c1315345234ba4eaae333e0dec20798682061" } }, "rootfs": { "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", "cacheKeys": { - "wasm32": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1", - "wasm64": "b99e1aa6075fb4c3baf261341d2698497978635320f1fcf6de25bf0d444b2237" + "wasm32": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8", + "wasm64": "b75a7e620d377a39adb35452f549d815e73561370140d80b9fdef670804869f4" } }, "ruby": { "manifestSha256": "9b8ab3aafc78e1a8c9e2f5b2f79a17976a6278ce52c23240d2c5a6f8cce3c52d", "cacheKeys": { - "wasm32": "c75e43269ce4d42c1f87291a152eafbba0158cf6ec9625ee62befaeabe14e2b2", - "wasm64": "b059d0f056c29d725d5b0a0e83ef0fef1df00d26cfd58aa586b22cb0a8fb3d97" + "wasm32": "14dd36525a93479b58177b3df9f030b83960f181a7c0cceeb265b2c8277e65e0", + "wasm64": "d7d6974e5b17f9dbcb7a336a19afab64211309f41c71c72597952767e53a9f23" } }, "sdl-dsp-test": { "manifestSha256": "4842ce3b88887a65c347dbb45a52d499f78f4c30430ce4cdfb3d5ec661ea0f67", "cacheKeys": { - "wasm32": "e36804a9f073136982cb60e7f52da451427dc850ab5760e99ed92b3ebe70daa0", - "wasm64": "02adf1994d507c4f6e04e0b43d3c2293d5d8610a722bb6dcb4812dc6702ce77d" + "wasm32": "2b6f207dfb1e1eccda5871c3aba08e4fb580bd9970436f35861fe51b5236f435", + "wasm64": "d2119a058f3d41b76e96f9ef819e72e863738f5f8b3c1805b5e1341441ca8bb1" } }, "sdl2": { "manifestSha256": "b6e6a01586cc01f6005fbaa79027b81a08dcc4cd77ff51a99a093d04ef930366", "cacheKeys": { - "wasm32": "064daacee36ac921255ee74911d5660b10499010a105fba8b365861890e70177", - "wasm64": "4c757a2ff021936ccd23fecfbae512afcf5b37a32b8f742ed170721664ff8bbb" + "wasm32": "5383b697827537e8a890edbd61d13bea292b3e1304009394ef4be33b697bf2f2", + "wasm64": "2338428c0d21dfa9109af25268b3cdae2dd38db1ca0cfe32142c6dddd89cfeec" } }, "sdl2-mixer-playwave": { "manifestSha256": "10b05fbd53e13fac3fcc4a5783022199b2ccaf94697b3a0de42330a88ef78ca5", "cacheKeys": { - "wasm32": "30dbc38b217dcb6e0a80cbcc4c3780aec647cccb76c2a3870a4da786a24a4d59", - "wasm64": "293c0d8566d0604bca52610505b3e274da626e6362d7daf661011fff01e1c113" + "wasm32": "64e111f88438f52575eb7fbbe95a93e32e3a05b68012f5d38ff35e63d4845220", + "wasm64": "112c3bb58d5ef1e9dd8b4715c644769f26056b1911a0c2185ce04abd7a18d1f6" } }, "sdl3": { "manifestSha256": "fc849fbc7da35401a040c328245b5e6d490e8f994b77c954ed2ab463d0892aca", "cacheKeys": { - "wasm32": "b8040a63705623aea889f697b1fed6fc58326c89fa2720dbfc89432f8b09d572", - "wasm64": "eb7ba370a6f027fa67b4e548e56671797f32b8df62b9a24ce39ddc26cb2bd6bb" + "wasm32": "5492484e32cf245385424cc9e73781d21ab919e89362418a0efdf48be3332119", + "wasm64": "970ebf01b2e50cdd74361283dd4310bf4f3fdfa19a7a86d296347d7cfa3177e3" } }, "sed": { "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", "cacheKeys": { - "wasm32": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024", - "wasm64": "2c5011d02e5dd2653cc951497157e7ba5088d6ff4898a9d4d4c650c4e8ed1fc0" + "wasm32": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9", + "wasm64": "5ee81879e1573be1b174a0fab99a129202e87dfbac09af03976d010246c18369" } }, "shell": { "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", "cacheKeys": { - "wasm32": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca", - "wasm64": "1e23fe9305f2f52c166f83df1725d5316e499d11843bc9a2f02fa4184358d1a3" + "wasm32": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e", + "wasm64": "b02ac7be30746a7d7d4e2f51fbdd199d9b2cd846abb710ee30110f9fbba9b6cf" } }, "spidermonkey": { "manifestSha256": "0460143a58135f834705ccd1316f9007f7d9bb1feee1de58574d2c6b2891ac40", "cacheKeys": { - "wasm32": "dc6d9e16ef5cb90621c7e083a38df6e0ca78f0c1ee7d08cda5e3eee159d8ae0a", - "wasm64": "ba678f6fc05490ce6fc65361c455db7ff4487d106f0a59bb5725ba877cc2541e" + "wasm32": "e19fa8ad12e75737398605395089a6f2ffff11a11c0e8fe8b1e6d1af005a0e05", + "wasm64": "765bd71da2116a988d4338d2b39b18d998313e5e8fdb6bcd83f5fc03791ac8f0" } }, "spidermonkey-node": { "manifestSha256": "a6bf29c653889576dec0914e45421ff05a374dc515613567990f7c52689921ff", "cacheKeys": { - "wasm32": "9dc5f6e47c3249ec4604dffa9d4f9097750574e97aa3c806c3d17cb9aba4e5a0", - "wasm64": "7c2eb0e3260aed664c30bfced29dd6769573a8bc9ff8557f5dd3cdbaad9c0502" + "wasm32": "63e3185dd513a0ba9760f5907fb7abd7bda32c5330e15f1a0298f84c8e158e8f", + "wasm64": "084d13708dc1328afe30a9d95f3a6c605809a64c71a09f1136ee3e0fea1f2c07" } }, "sqlite": { "manifestSha256": "77110d20137fe7dfd55d6be0f99c959ec3d4bc23ebbf516e5eb51f3886d8bcee", "cacheKeys": { - "wasm32": "225473952aaa1f400b89870afc6943881cbbbbbe96019b5b7fd47e913a29fcb2", - "wasm64": "8247eefd3a48051a1a7fff31f9a44658610c56a7601a05267133cb221d609a16" + "wasm32": "fce7d13e41a79094256bc45771a13581e2c699dead5a29841561c76d435425c6", + "wasm64": "85f4853b1c10c8fff49ad60136480ecac85dee4c7d958c35731a3562c7209964" } }, "sqlite-cli": { "manifestSha256": "1cebb768f9473dc58fc6e72c9b24565760aedaec4cbdb29ad43b22a0a5fd7030", "cacheKeys": { - "wasm32": "6b64a2825e98a8d41b7f6d19093080092591b74b20717cdbb66e9508e21bef66", - "wasm64": "d9a89f33a07a9eb62abbd022698d57a944fa614034c6243f900d3b2ab39c5a17" + "wasm32": "e842a724c62debb88c9381709205d21869a4989127a51448a19dbd92f2ae8137", + "wasm64": "27c3ec7f7d530527deed8f0dff684a767af9d29db1dbee101f70022d5a2e0b38" } }, "tar": { "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", "cacheKeys": { - "wasm32": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068", - "wasm64": "ef88ea445cd39db8f54a6fdcd307429cb55fb65ab23f649e33a17f5d6f56b05f" + "wasm32": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923", + "wasm64": "0b0d0730dc661c5b4b2cffecd832035bcf2694f6559e6edc0c43e68448b9cd24" } }, "tcl": { "manifestSha256": "3a92ed6294ffb22589cec3ef1551a90065a47e0ff7d62ee95dcb0b38a64c5781", "cacheKeys": { - "wasm32": "570b6000d025ccaed627b2c38902c7c0f1b6f1352255de8dbe2dc6f76d4147cd", - "wasm64": "46113f5b5849423755e8ebb9541d49206cedfe19fb4c7fd9fd84ad5cbb919416" + "wasm32": "a6c314bbab19e91bc004a6aba12cee9c1ef081154c3424a0a91dde67bf39370b", + "wasm64": "c8f58feec55113b40eac1cac68cbf52a68cb58e165310f0746b4647c2a7dacf1" } }, "texlive": { "manifestSha256": "028ada023f8a8f9966c8a28575242a339aaaf0e8870fe4a221986133b381c3c0", "cacheKeys": { - "wasm32": "599e13a4bd4426240dd18c50d176d032fe67deb80d431b7f983cc8eec85adeb4", - "wasm64": "2c41f509ab90a82c7c4072f04bb091155630151ebba1e0256882d3b562b44eba" + "wasm32": "e323130e3c0a89e934b1568bc43ba464655b8f4a8a756524840cc75ef1d65482", + "wasm64": "72cd6f416a4d376b6465d7789fb35cd532c76b7df87959ec402fe6b79c73a5a6" } }, "unzip": { "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", "cacheKeys": { - "wasm32": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033", - "wasm64": "2ae40aad671cd27771d98a0d3acb79e5f5671618c3f54fc7a4c4d22ac27bb37f" + "wasm32": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85", + "wasm64": "2cbe655687fa1fe409e9ab2008bd9db8ed8b27a860601126f1e045f9ef97645f" } }, "userspace": { "manifestSha256": "e18c1dd7a811e09b336d20dea545e54d14ee8e35da545a5cde8721d301a62c85", "cacheKeys": { - "wasm32": "c255fe62d44fb7f8d741c4fcd99cd87c7048cd76d4b08db86f026c81aec713a8", - "wasm64": "d68bad5cad1736f502c027d0948fba43c9a0da51ced3be59371708c88b2abca4" + "wasm32": "2279ef997b2635a3b69e93f530e4f95bc92cece58ed8fd6d2dc6c8201aebf2a4", + "wasm64": "efed20ce94a9a4bfd81dc2804fb003687945f25f25ac4d9c986628c3394319a5" } }, "vim": { "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", "cacheKeys": { - "wasm32": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa", - "wasm64": "89d61a7fb76cea51aaa97feefabc2934386347b23ab3649b5efc834b8bfdf8eb" + "wasm32": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea", + "wasm64": "edf1e47c494b22994341f024c320c4d06c763100e98374c723a7da7511ab7c6c" } }, "vim-browser-bundle": { "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", "cacheKeys": { - "wasm32": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51", - "wasm64": "886ce9d0a2d9bc2387f5718a4e9856a423e1a244a6422b0d47bda947982db2d2" + "wasm32": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61", + "wasm64": "c79ef76a7d922c4929e45020c7dc271b931c1164c72b02b3abd9971a6e4d7ca4" } }, "wget": { "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", "cacheKeys": { - "wasm32": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142", - "wasm64": "8d58d6206b7abf3826f7667553c013c5777ab00b1a71693869c443aa029e803a" + "wasm32": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1", + "wasm64": "c84a4e6fad4d2be3dc55d18da0e081c54396a8473ad638ccacfe416e13002f52" } }, "wordpress": { "manifestSha256": "346cbc47088979d57da3ecf79d0607ee5e2463f7e4b0e01a48a983e29f059e3d", "cacheKeys": { - "wasm32": "18970d4c019d106a8c99af8e62e4db0bb3bc1d2c1a631f8be4ca29f856e996e3", - "wasm64": "40ef860252187ab2c792952c7a19ab3927305e79a419c6f7aaccb87359dc6a53" + "wasm32": "420af5ddd540a93ae4fa87a205066b8417ffe60a558c82ee20b1438797eb863a", + "wasm64": "45eb13ad56d03bb01267038caae7a9c561b02d32b088e77ddbfceda2640be3d5" } }, "wordpress-sqlite-integration-source": { @@ -557,29 +571,29 @@ "xz": { "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", "cacheKeys": { - "wasm32": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450", - "wasm64": "77fb8df1199e83cec2628474530840881d43af2e16c95f33b469c7d9590a240e" + "wasm32": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c", + "wasm64": "269de0fbfe0f6cc1745a467f23afae440b45202bcdd9e7866e7e229c76806032" } }, "zip": { "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", "cacheKeys": { - "wasm32": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862", - "wasm64": "3bef9e9ca2235546ec8da2dce3d053eb43a8fe45d69b376d0c559f6cbd3f94e9" + "wasm32": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8", + "wasm64": "2afbebd734e99f3da937f8473a0d5f4fbcb7e5f94aeba96704853c8181d23b38" } }, "zlib": { "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", "cacheKeys": { - "wasm32": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6", - "wasm64": "d49126e6fa312c3a1f93c4c51803ce01a4df3c50a4fe4f2b5ebf5e0d4b172a81" + "wasm32": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce", + "wasm64": "0ab07005ed9a8837205721ebdf7ad49fe1e3eb36601b5485d52aeada4ff1ef7c" } }, "zstd": { "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", "cacheKeys": { - "wasm32": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec", - "wasm64": "b5b4767826d5c3d6caa32dd9fc14e77271fba6cc7b213c74a1a2879d89343c42" + "wasm32": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89", + "wasm64": "c167e5a971e802ae56703eb0e52a02ccf21a542f0485b1a2db51df6fb489d3e1" } } }, @@ -590,14 +604,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "wasm32": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, "dependencyClosures": { "wasm32": [ { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" } ] }, @@ -617,7 +631,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "wasm32": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, "dependencyClosures": { "wasm32": [] @@ -638,7 +652,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "wasm32": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, "dependencyClosures": { "wasm32": [] @@ -659,7 +673,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "wasm32": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, "dependencyClosures": { "wasm32": [] @@ -680,14 +694,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "671600d839333cd841b35d44f182836e9d9f515b0da5f13ece812b15c99aa797" + "wasm32": "24754c020c2a08b5cf2502ea4f3010c53c1c0b4c2094fa5d597221d36afc29b1" }, "dependencyClosures": { "wasm32": [ { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -714,19 +728,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "wasm32": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, "dependencyClosures": { "wasm32": [ { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -746,7 +760,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "wasm32": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, "dependencyClosures": { "wasm32": [] @@ -767,7 +781,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "wasm32": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, "dependencyClosures": { "wasm32": [] @@ -809,14 +823,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "wasm32": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" } ] }, @@ -850,7 +864,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "54c816a671fdaa12a5ab18580c4e54d423f546c56cb5a237449f090c68cda9d8" + "wasm32": "a84f5187db542cc4a7f68d9747a5e9096e610a261335e629e785eb4b798f79b3" }, "dependencyClosures": { "wasm32": [] @@ -878,14 +892,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "0d2025175cc7675a3d69bef53ffb084b8b2bb719fca8f437147ca36341c28014" + "wasm32": "a959310a47cb3b6e483ce0737ea31ca4c3cef6361e40b9ec28c2cfc38efa5f52" }, "dependencyClosures": { "wasm32": [ { "packageName": "erlang", "manifestSha256": "475d6037e73a0f1e2f4381067194b2422751206979ea17209e10efa5a2a93bbb", - "cacheKey": "54c816a671fdaa12a5ab18580c4e54d423f546c56cb5a237449f090c68cda9d8" + "cacheKey": "a84f5187db542cc4a7f68d9747a5e9096e610a261335e629e785eb4b798f79b3" } ] }, @@ -899,13 +913,68 @@ } ] }, + "espeak-ng": { + "manifestSha256": "ae6c7b301617b5541bbf9fb9ff68a886f05c6e19f5393eacf03b50172ae5477d", + "arches": [ + "wasm32" + ], + "cacheKeys": { + "wasm32": "0c3735cdb7871c3782a3494617163b6c5294db3d8afe6aaa1a6b7ec56b2e2d4a" + }, + "dependencyClosures": { + "wasm32": [ + { + "packageName": "libcxx", + "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" + } + ] + }, + "members": [ + { + "kind": "output", + "sourceArtifact": "espeak-ng.wasm", + "mirrorPath": "espeak-ng/espeak-ng.wasm", + "outputName": "espeak-ng", + "forkInstrumentation": "auto" + }, + { + "kind": "runtime-file", + "sourceArtifact": "espeak-ng-data.zip", + "mirrorPath": "espeak-ng/espeak-ng-data.zip", + "guestPath": "/usr/share/espeak-ng/espeak-ng-data.zip", + "mode": 420 + } + ] + }, + "evdev-demo": { + "manifestSha256": "79e367095cbe154238d3b54b472fdea8426e4aa9d3a96afce61b04f656126952", + "arches": [ + "wasm32" + ], + "cacheKeys": { + "wasm32": "0cb7324a23a8380757d5e5921e6f73f4c55ccd983f72d129d3ea4567adbf5ffe" + }, + "dependencyClosures": { + "wasm32": [] + }, + "members": [ + { + "kind": "output", + "sourceArtifact": "evdev_demo.wasm", + "mirrorPath": "evdev_demo.wasm", + "outputName": "evdev_demo", + "forkInstrumentation": "auto" + } + ] + }, "fbdoom": { "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", "arches": [ "wasm32" ], "cacheKeys": { - "wasm32": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "wasm32": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, "dependencyClosures": { "wasm32": [] @@ -926,7 +995,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "wasm32": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, "dependencyClosures": { "wasm32": [] @@ -954,7 +1023,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "wasm32": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, "dependencyClosures": { "wasm32": [] @@ -982,7 +1051,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "wasm32": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, "dependencyClosures": { "wasm32": [] @@ -1003,24 +1072,24 @@ "wasm32" ], "cacheKeys": { - "wasm32": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "wasm32": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -1047,7 +1116,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "wasm32": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, "dependencyClosures": { "wasm32": [] @@ -1068,7 +1137,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "wasm32": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, "dependencyClosures": { "wasm32": [] @@ -1089,7 +1158,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "bf2afee293e5c1c5520beb9311d410b81be3140134d414f51c3580dd5c672fa4" + "wasm32": "91b465b6881947705159383ae7f518d0877628f55f6a54ae201f9d764c2315e4" }, "dependencyClosures": { "wasm32": [] @@ -1117,14 +1186,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "325158d5bb1f323c3f99650703e0123c26dda5abbf2bf6c2bfc814ae6a70d304" + "wasm32": "1b32cb3488a50b98494858b057e82eb29f53cbce1d27d40480bb1559158a5735" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" } ] }, @@ -1144,189 +1213,189 @@ "wasm32" ], "cacheKeys": { - "wasm32": "d206b4754b798d0d45e5f2c8c3a7d4f6f08eececd660ad8cf484d6ddcac1e0e2" + "wasm32": "5c9dc3d7f0101fbcb958ce8517938789071796d02de39c05730effe81437318b" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "icu", "manifestSha256": "e1169851c1978d3356c4e4c0f396672cb6b7785375a44411f7211ddfc90e7ad5", - "cacheKey": "29b737564e6d227336ef5ab8853ea2a2bf18b6d01f4619e04252cb9a66aea401" + "cacheKey": "d3da6e8512e1edd9452bf2f7898756037a1074bbedf191912c6bbbb827ef245a" }, { "packageName": "kernel", "manifestSha256": "3cfdfa2130a0917b23ee9ae760901e5caa225654169a51f767413c38025b7b2d", - "cacheKey": "6f7364081f0971d3948eaf7c3f8b11b00b0ce98bc9d1525c75d03ef056f65d71" + "cacheKey": "db62f6bd1409ee6c7f465f21d796dc4717d63fa2bed8bb2c6e7d86cbc4c5b217" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "libiconv", "manifestSha256": "7ae4f68412db04f01e027f94b0d14c1130ac4dfb2041270207994ddab56f0855", - "cacheKey": "939a64271c7d51277a5ec7a49c45c55fb4c4b252c68361ca3c6beb6bf9a3111b" + "cacheKey": "2bc2df82f5537c5f74f7e06dc3e6375f4ef24a0866e54df0f2e75f0152baf538" }, { "packageName": "libxml2", "manifestSha256": "87301bfcb607ee24c00925787cfe071ba506c750437ff5b807b2ea0897058e91", - "cacheKey": "0f93f0ef28cebb24d87516d0db6683a8909359a10f1577db92a8374b04ae7cdd" + "cacheKey": "a90b8526a47a799b2917d4dc3c6d16f1d65ece66005e484a322c503e5c5805d8" }, { "packageName": "libzip", "manifestSha256": "84a6e61fc67c7dd6cf5b4aacd80682a86d402c78810efdd03a9ff58da8eaf6a0", - "cacheKey": "f7b217af4f83eeb207d7cbfca570144719a67f4df6ae3c1fc56a8694dd0568ce" + "cacheKey": "7ddffc7b50eea7a8ecebc94f88c1f6e6953b1d1bfd0bb30bb6f33805c3f100da" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "mariadb", "manifestSha256": "2e0699de7658d2ae002556c84f49ccb61a70e96b500b41c5e73246570d921bea", - "cacheKey": "48bc77b348e61faa8b0e7430fa8cc02cf0136548cfd6b342bb3d7dfccf9c138b" + "cacheKey": "11a884f2ab079b77a5476dca2475c0ab8f693c0fd9078a9218fcd03f81e1c4d0" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "msmtpd", "manifestSha256": "eb554efa7f11b5d2c2268492eecf35bc0d80eeebbcffc1441b590b5541b4ec16", - "cacheKey": "9a013fcbb76a91f84bcf10962e77fb9f79ab66ad8ab9cc066d1053a92aa6a4a7" + "cacheKey": "7c8c1a37ebb098b5648349ac9d1600b212ace546c6d8ef87258e6796d78e0be1" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "nginx", "manifestSha256": "b78552502c63bef2814c8aae76fedfdb4f273e4ee0c42023d6157ddb298ff815", - "cacheKey": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640" + "cacheKey": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "pcre2-source", @@ -1336,77 +1405,77 @@ { "packageName": "php", "manifestSha256": "14114e280941f1f2f6a4c5abc11698b4b01f909be4cf2aac5b0e68845fed4644", - "cacheKey": "6062638f2f20a7254948ee6bede9c4a1515d0ff4565abb15084b037c3510ab05" + "cacheKey": "70a85fce082656d22b33c356285b2659a00ff59e3efeda9f35862e8fdd6c0abd" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "shell", "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", - "cacheKey": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "cacheKey": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, { "packageName": "sqlite", "manifestSha256": "77110d20137fe7dfd55d6be0f99c959ec3d4bc23ebbf516e5eb51f3886d8bcee", - "cacheKey": "225473952aaa1f400b89870afc6943881cbbbbbe96019b5b7fd47e913a29fcb2" + "cacheKey": "fce7d13e41a79094256bc45771a13581e2c699dead5a29841561c76d435425c6" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -1426,7 +1495,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "wasm32": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, "dependencyClosures": { "wasm32": [] @@ -1447,7 +1516,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "wasm32": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, "dependencyClosures": { "wasm32": [] @@ -1468,7 +1537,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "wasm32": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, "dependencyClosures": { "wasm32": [] @@ -1489,7 +1558,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "wasm32": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, "dependencyClosures": { "wasm32": [] @@ -1511,15 +1580,15 @@ "wasm64" ], "cacheKeys": { - "wasm32": "48bc77b348e61faa8b0e7430fa8cc02cf0136548cfd6b342bb3d7dfccf9c138b", - "wasm64": "7ecdf7257a8395280927964eecda07d31a6eacd1f09f9a0775a5ee29876f87db" + "wasm32": "11a884f2ab079b77a5476dca2475c0ab8f693c0fd9078a9218fcd03f81e1c4d0", + "wasm64": "f1dd516fed2d03f9699d77d69b5abcece8ebfe03628cc208f8d0d1c5116608f2" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "pcre2-source", @@ -1531,7 +1600,7 @@ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "5fed4cef387e8aa1dba438ab123b54e9324dc7b33a448a31d203f78df73b1d02" + "cacheKey": "67b0dd58954a0d9f9433bcdb5d5064f3575bd48ba84dd7ca7ff1400ea0181467" }, { "packageName": "pcre2-source", @@ -1577,34 +1646,34 @@ "wasm32" ], "cacheKeys": { - "wasm32": "dca8b89a45f43c9ed3b079d8a5505d4870724ffeb84e20f404668a631f1a0263" + "wasm32": "c416deee68dc98a54c46f762ff9ba69363cb77f647bc3093855494f022f21936" }, "dependencyClosures": { "wasm32": [ { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "mariadb", "manifestSha256": "2e0699de7658d2ae002556c84f49ccb61a70e96b500b41c5e73246570d921bea", - "cacheKey": "48bc77b348e61faa8b0e7430fa8cc02cf0136548cfd6b342bb3d7dfccf9c138b" + "cacheKey": "11a884f2ab079b77a5476dca2475c0ab8f693c0fd9078a9218fcd03f81e1c4d0" }, { "packageName": "pcre2-source", @@ -1630,35 +1699,35 @@ "wasm64" ], "cacheKeys": { - "wasm32": "a41210be1a20284debb0b47837090c26aefda421515ba67c126fc9b1f9604ff7", - "wasm64": "000555b6d6f44d64e84211d2963cc30e733beff57ceebd0dc04444e5cfb034e9" + "wasm32": "38422a41bba8901715bd540b312b7eb3cf6381eb55bd5640524ea3bbbea10367", + "wasm64": "3858257711ebd3f6250a2085c03e69662ae95e44abb6d906400b908d5094e92b" }, "dependencyClosures": { "wasm32": [ { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "mariadb", "manifestSha256": "2e0699de7658d2ae002556c84f49ccb61a70e96b500b41c5e73246570d921bea", - "cacheKey": "48bc77b348e61faa8b0e7430fa8cc02cf0136548cfd6b342bb3d7dfccf9c138b" + "cacheKey": "11a884f2ab079b77a5476dca2475c0ab8f693c0fd9078a9218fcd03f81e1c4d0" }, { "packageName": "pcre2-source", @@ -1670,27 +1739,27 @@ { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "ed081404db705f2d4dcf3a1e5319c4a22809dff9b84b9fd0bf4b3ae3f3913e3b" + "cacheKey": "718d3028065caf74d3e9d6f4563133690dcd44eeb12dfc20e2efe790315770c4" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "8a658122b3dbef11e1a4cdbff2b56b9756cde0e4c3942c3398cfa01046da3cc0" + "cacheKey": "bf94c26f54f24404557058c49c772d0f25cab107d0b1a3a8a937fb6fdb71713a" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "cf9024c623d71b848048b4b68ace29ed182fe173df89b56ed02177018a557ce7" + "cacheKey": "986479c451806fa661c3a1773f3aea1d1390603e1fe547c661d298fb2d1df1e7" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "5fed4cef387e8aa1dba438ab123b54e9324dc7b33a448a31d203f78df73b1d02" + "cacheKey": "67b0dd58954a0d9f9433bcdb5d5064f3575bd48ba84dd7ca7ff1400ea0181467" }, { "packageName": "mariadb", "manifestSha256": "2e0699de7658d2ae002556c84f49ccb61a70e96b500b41c5e73246570d921bea", - "cacheKey": "7ecdf7257a8395280927964eecda07d31a6eacd1f09f9a0775a5ee29876f87db" + "cacheKey": "f1dd516fed2d03f9699d77d69b5abcece8ebfe03628cc208f8d0d1c5116608f2" }, { "packageName": "pcre2-source", @@ -1715,7 +1784,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "wasm32": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, "dependencyClosures": { "wasm32": [] @@ -1736,7 +1805,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "9a013fcbb76a91f84bcf10962e77fb9f79ab66ad8ab9cc066d1053a92aa6a4a7" + "wasm32": "7c8c1a37ebb098b5648349ac9d1600b212ace546c6d8ef87258e6796d78e0be1" }, "dependencyClosures": { "wasm32": [] @@ -1757,14 +1826,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "wasm32": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, "dependencyClosures": { "wasm32": [ { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" } ] }, @@ -1784,7 +1853,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "wasm32": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, "dependencyClosures": { "wasm32": [] @@ -1868,7 +1937,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "wasm32": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, "dependencyClosures": { "wasm32": [] @@ -1889,14 +1958,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "wasm32": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, "dependencyClosures": { "wasm32": [ { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" } ] }, @@ -1916,19 +1985,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "wasm32": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, "dependencyClosures": { "wasm32": [ { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" } ] }, @@ -1948,7 +2017,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640" + "wasm32": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a" }, "dependencyClosures": { "wasm32": [] @@ -1969,254 +2038,254 @@ "wasm32" ], "cacheKeys": { - "wasm32": "e90acec39405a648ced9a361bc1008b198322d5d2abb40b32aaaecff59379b09" + "wasm32": "e4462832b24f124b23314eb26add749ca515fe1a38f78834816c0fae0c49da82" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "icu", "manifestSha256": "e1169851c1978d3356c4e4c0f396672cb6b7785375a44411f7211ddfc90e7ad5", - "cacheKey": "29b737564e6d227336ef5ab8853ea2a2bf18b6d01f4619e04252cb9a66aea401" + "cacheKey": "d3da6e8512e1edd9452bf2f7898756037a1074bbedf191912c6bbbb827ef245a" }, { "packageName": "kernel", "manifestSha256": "3cfdfa2130a0917b23ee9ae760901e5caa225654169a51f767413c38025b7b2d", - "cacheKey": "6f7364081f0971d3948eaf7c3f8b11b00b0ce98bc9d1525c75d03ef056f65d71" + "cacheKey": "db62f6bd1409ee6c7f465f21d796dc4717d63fa2bed8bb2c6e7d86cbc4c5b217" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "libiconv", "manifestSha256": "7ae4f68412db04f01e027f94b0d14c1130ac4dfb2041270207994ddab56f0855", - "cacheKey": "939a64271c7d51277a5ec7a49c45c55fb4c4b252c68361ca3c6beb6bf9a3111b" + "cacheKey": "2bc2df82f5537c5f74f7e06dc3e6375f4ef24a0866e54df0f2e75f0152baf538" }, { "packageName": "libxml2", "manifestSha256": "87301bfcb607ee24c00925787cfe071ba506c750437ff5b807b2ea0897058e91", - "cacheKey": "0f93f0ef28cebb24d87516d0db6683a8909359a10f1577db92a8374b04ae7cdd" + "cacheKey": "a90b8526a47a799b2917d4dc3c6d16f1d65ece66005e484a322c503e5c5805d8" }, { "packageName": "libzip", "manifestSha256": "84a6e61fc67c7dd6cf5b4aacd80682a86d402c78810efdd03a9ff58da8eaf6a0", - "cacheKey": "f7b217af4f83eeb207d7cbfca570144719a67f4df6ae3c1fc56a8694dd0568ce" + "cacheKey": "7ddffc7b50eea7a8ecebc94f88c1f6e6953b1d1bfd0bb30bb6f33805c3f100da" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "nginx", "manifestSha256": "b78552502c63bef2814c8aae76fedfdb4f273e4ee0c42023d6157ddb298ff815", - "cacheKey": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640" + "cacheKey": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "php", "manifestSha256": "14114e280941f1f2f6a4c5abc11698b4b01f909be4cf2aac5b0e68845fed4644", - "cacheKey": "6062638f2f20a7254948ee6bede9c4a1515d0ff4565abb15084b037c3510ab05" + "cacheKey": "70a85fce082656d22b33c356285b2659a00ff59e3efeda9f35862e8fdd6c0abd" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "shell", "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", - "cacheKey": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "cacheKey": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, { "packageName": "sqlite", "manifestSha256": "77110d20137fe7dfd55d6be0f99c959ec3d4bc23ebbf516e5eb51f3886d8bcee", - "cacheKey": "225473952aaa1f400b89870afc6943881cbbbbbe96019b5b7fd47e913a29fcb2" + "cacheKey": "fce7d13e41a79094256bc45771a13581e2c699dead5a29841561c76d435425c6" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -2236,219 +2305,219 @@ "wasm32" ], "cacheKeys": { - "wasm32": "e9a37566343366381720bd5d48cb5bffd3b74c1164901ce8e20970279332948c" + "wasm32": "d3df523ac43da00f2dde4c938389e7f6bff55c86bf582a8300b519ee412e23e3" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "nginx", "manifestSha256": "b78552502c63bef2814c8aae76fedfdb4f273e4ee0c42023d6157ddb298ff815", - "cacheKey": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640" + "cacheKey": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "shell", "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", - "cacheKey": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "cacheKey": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -2468,29 +2537,29 @@ "wasm32" ], "cacheKeys": { - "wasm32": "010787fcdad88941f9bb4fda4d3b19b3c479de269ffd5d47588e25bdf375ab1e" + "wasm32": "d090c5dffda26124b0fd2f6c439e9346fd336ab65e5e98c27f734fc30627a0f2" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "spidermonkey", "manifestSha256": "0460143a58135f834705ccd1316f9007f7d9bb1feee1de58574d2c6b2891ac40", - "cacheKey": "dc6d9e16ef5cb90621c7e083a38df6e0ca78f0c1ee7d08cda5e3eee159d8ae0a" + "cacheKey": "e19fa8ad12e75737398605395089a6f2ffff11a11c0e8fe8b1e6d1af005a0e05" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -2510,219 +2579,219 @@ "wasm32" ], "cacheKeys": { - "wasm32": "bdcaae9a5dc421e4a4fbd7cebac8b628c585923f75562ca0917e176c39b36aa9" + "wasm32": "7a6c0893e1404bcf91813971d297e87afcd96363308731e1364bce5c4458bfdb" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "node", "manifestSha256": "353bce1b61a21b2132340a9f6f1e2d7e9354a592b3d9762165a215ad390c23c2", - "cacheKey": "010787fcdad88941f9bb4fda4d3b19b3c479de269ffd5d47588e25bdf375ab1e" + "cacheKey": "d090c5dffda26124b0fd2f6c439e9346fd336ab65e5e98c27f734fc30627a0f2" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "shell", "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", - "cacheKey": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "cacheKey": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, { "packageName": "spidermonkey", "manifestSha256": "0460143a58135f834705ccd1316f9007f7d9bb1feee1de58574d2c6b2891ac40", - "cacheKey": "dc6d9e16ef5cb90621c7e083a38df6e0ca78f0c1ee7d08cda5e3eee159d8ae0a" + "cacheKey": "e19fa8ad12e75737398605395089a6f2ffff11a11c0e8fe8b1e6d1af005a0e05" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -2742,7 +2811,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "e4ec3cdc5b7ceacb9b9be1d93ff3c52247e95d8ec3e07927b6c3fb059909740f" + "wasm32": "a96df7fab0a5a61a0541f75698b2a963ee80732d6efad7dc57a6094c80d0a7d7" }, "dependencyClosures": { "wasm32": [] @@ -2763,14 +2832,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "a8a53f1dfe72eec9bfd6c6867aa9c0babd6fc2783988747109173ba605bab1e1" + "wasm32": "7a52dff193297d1d438d032687fad3debbb98ed6b70ac9a6f56547d86c65ee5e" }, "dependencyClosures": { "wasm32": [ { "packageName": "perl", "manifestSha256": "dfebfffe4136be5b38e5f7aef14d75068e42ac9a05541582d005a6992f41c216", - "cacheKey": "e4ec3cdc5b7ceacb9b9be1d93ff3c52247e95d8ec3e07927b6c3fb059909740f" + "cacheKey": "a96df7fab0a5a61a0541f75698b2a963ee80732d6efad7dc57a6094c80d0a7d7" } ] }, @@ -2790,54 +2859,54 @@ "wasm32" ], "cacheKeys": { - "wasm32": "6062638f2f20a7254948ee6bede9c4a1515d0ff4565abb15084b037c3510ab05" + "wasm32": "70a85fce082656d22b33c356285b2659a00ff59e3efeda9f35862e8fdd6c0abd" }, "dependencyClosures": { "wasm32": [ { "packageName": "icu", "manifestSha256": "e1169851c1978d3356c4e4c0f396672cb6b7785375a44411f7211ddfc90e7ad5", - "cacheKey": "29b737564e6d227336ef5ab8853ea2a2bf18b6d01f4619e04252cb9a66aea401" + "cacheKey": "d3da6e8512e1edd9452bf2f7898756037a1074bbedf191912c6bbbb827ef245a" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "libiconv", "manifestSha256": "7ae4f68412db04f01e027f94b0d14c1130ac4dfb2041270207994ddab56f0855", - "cacheKey": "939a64271c7d51277a5ec7a49c45c55fb4c4b252c68361ca3c6beb6bf9a3111b" + "cacheKey": "2bc2df82f5537c5f74f7e06dc3e6375f4ef24a0866e54df0f2e75f0152baf538" }, { "packageName": "libxml2", "manifestSha256": "87301bfcb607ee24c00925787cfe071ba506c750437ff5b807b2ea0897058e91", - "cacheKey": "0f93f0ef28cebb24d87516d0db6683a8909359a10f1577db92a8374b04ae7cdd" + "cacheKey": "a90b8526a47a799b2917d4dc3c6d16f1d65ece66005e484a322c503e5c5805d8" }, { "packageName": "libzip", "manifestSha256": "84a6e61fc67c7dd6cf5b4aacd80682a86d402c78810efdd03a9ff58da8eaf6a0", - "cacheKey": "f7b217af4f83eeb207d7cbfca570144719a67f4df6ae3c1fc56a8694dd0568ce" + "cacheKey": "7ddffc7b50eea7a8ecebc94f88c1f6e6953b1d1bfd0bb30bb6f33805c3f100da" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "sqlite", "manifestSha256": "77110d20137fe7dfd55d6be0f99c959ec3d4bc23ebbf516e5eb51f3886d8bcee", - "cacheKey": "225473952aaa1f400b89870afc6943881cbbbbbe96019b5b7fd47e913a29fcb2" + "cacheKey": "fce7d13e41a79094256bc45771a13581e2c699dead5a29841561c76d435425c6" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -2913,7 +2982,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "wasm32": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, "dependencyClosures": { "wasm32": [] @@ -3186,19 +3255,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "d99fdd4b6bac0a13639e1e1a99bcd10a5122f4f2fa86d908c9a6267219bc7ee1" + "wasm32": "79403e28bbf504b073ee6c4bcadffe3344e2004454eb5d906118b11e6ade602c" }, "dependencyClosures": { "wasm32": [ { "packageName": "cpython", "manifestSha256": "3e5bd98bf8221902ca214d73117fa71d7dfeb0c75cf5c1fe219b82eb94bb9ff0", - "cacheKey": "671600d839333cd841b35d44f182836e9d9f515b0da5f13ece812b15c99aa797" + "cacheKey": "24754c020c2a08b5cf2502ea4f3010c53c1c0b4c2094fa5d597221d36afc29b1" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3218,7 +3287,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "4a90606ee4c37280f794d9b6249b80f8533c76a9148ee86ebc0ca609d437ba05" + "wasm32": "c6d0e0623fd00ec516ed08d36771c34cc0c76019bf3a65bda82371e0d49e3239" }, "dependencyClosures": { "wasm32": [] @@ -3246,24 +3315,24 @@ "wasm32" ], "cacheKeys": { - "wasm32": "13a85bd7b4cfadbebf9d2aa3966988dac7e37aea7897453a6eaa4bc8e9f9ab0d" + "wasm32": "bc088fa32d51ca9ad14533e12f66ed674cb3fc9e92057086b6ccf6ed214a7462" }, "dependencyClosures": { "wasm32": [ { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "redis", "manifestSha256": "94bfd24dd43bea67bb036fa52e8ab9a88ab5c29d9597e2a7e32b30cac9474d42", - "cacheKey": "4a90606ee4c37280f794d9b6249b80f8533c76a9148ee86ebc0ca609d437ba05" + "cacheKey": "c6d0e0623fd00ec516ed08d36771c34cc0c76019bf3a65bda82371e0d49e3239" } ] }, @@ -3283,79 +3352,79 @@ "wasm32" ], "cacheKeys": { - "wasm32": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "wasm32": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" } ] }, @@ -3375,14 +3444,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "c75e43269ce4d42c1f87291a152eafbba0158cf6ec9625ee62befaeabe14e2b2" + "wasm32": "14dd36525a93479b58177b3df9f030b83960f181a7c0cceeb265b2c8277e65e0" }, "dependencyClosures": { "wasm32": [ { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3409,19 +3478,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "e36804a9f073136982cb60e7f52da451427dc850ab5760e99ed92b3ebe70daa0" + "wasm32": "2b6f207dfb1e1eccda5871c3aba08e4fb580bd9970436f35861fe51b5236f435" }, "dependencyClosures": { "wasm32": [ { "packageName": "sdl2", "manifestSha256": "b6e6a01586cc01f6005fbaa79027b81a08dcc4cd77ff51a99a093d04ef930366", - "cacheKey": "064daacee36ac921255ee74911d5660b10499010a105fba8b365861890e70177" + "cacheKey": "5383b697827537e8a890edbd61d13bea292b3e1304009394ef4be33b697bf2f2" }, { "packageName": "sdl3", "manifestSha256": "fc849fbc7da35401a040c328245b5e6d490e8f994b77c954ed2ab463d0892aca", - "cacheKey": "b8040a63705623aea889f697b1fed6fc58326c89fa2720dbfc89432f8b09d572" + "cacheKey": "5492484e32cf245385424cc9e73781d21ab919e89362418a0efdf48be3332119" } ] }, @@ -3448,14 +3517,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "30dbc38b217dcb6e0a80cbcc4c3780aec647cccb76c2a3870a4da786a24a4d59" + "wasm32": "64e111f88438f52575eb7fbbe95a93e32e3a05b68012f5d38ff35e63d4845220" }, "dependencyClosures": { "wasm32": [ { "packageName": "sdl2", "manifestSha256": "b6e6a01586cc01f6005fbaa79027b81a08dcc4cd77ff51a99a093d04ef930366", - "cacheKey": "064daacee36ac921255ee74911d5660b10499010a105fba8b365861890e70177" + "cacheKey": "5383b697827537e8a890edbd61d13bea292b3e1304009394ef4be33b697bf2f2" } ] }, @@ -3475,7 +3544,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "wasm32": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, "dependencyClosures": { "wasm32": [] @@ -3496,199 +3565,199 @@ "wasm32" ], "cacheKeys": { - "wasm32": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "wasm32": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -3708,24 +3777,24 @@ "wasm32" ], "cacheKeys": { - "wasm32": "dc6d9e16ef5cb90621c7e083a38df6e0ca78f0c1ee7d08cda5e3eee159d8ae0a" + "wasm32": "e19fa8ad12e75737398605395089a6f2ffff11a11c0e8fe8b1e6d1af005a0e05" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3745,29 +3814,29 @@ "wasm32" ], "cacheKeys": { - "wasm32": "9dc5f6e47c3249ec4604dffa9d4f9097750574e97aa3c806c3d17cb9aba4e5a0" + "wasm32": "63e3185dd513a0ba9760f5907fb7abd7bda32c5330e15f1a0298f84c8e158e8f" }, "dependencyClosures": { "wasm32": [ { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "spidermonkey", "manifestSha256": "0460143a58135f834705ccd1316f9007f7d9bb1feee1de58574d2c6b2891ac40", - "cacheKey": "dc6d9e16ef5cb90621c7e083a38df6e0ca78f0c1ee7d08cda5e3eee159d8ae0a" + "cacheKey": "e19fa8ad12e75737398605395089a6f2ffff11a11c0e8fe8b1e6d1af005a0e05" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3787,7 +3856,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "6b64a2825e98a8d41b7f6d19093080092591b74b20717cdbb66e9508e21bef66" + "wasm32": "e842a724c62debb88c9381709205d21869a4989127a51448a19dbd92f2ae8137" }, "dependencyClosures": { "wasm32": [] @@ -3808,7 +3877,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "wasm32": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, "dependencyClosures": { "wasm32": [] @@ -3829,7 +3898,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "570b6000d025ccaed627b2c38902c7c0f1b6f1352255de8dbe2dc6f76d4147cd" + "wasm32": "a6c314bbab19e91bc004a6aba12cee9c1ef081154c3424a0a91dde67bf39370b" }, "dependencyClosures": { "wasm32": [] @@ -3850,19 +3919,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "599e13a4bd4426240dd18c50d176d032fe67deb80d431b7f983cc8eec85adeb4" + "wasm32": "e323130e3c0a89e934b1568bc43ba464655b8f4a8a756524840cc75ef1d65482" }, "dependencyClosures": { "wasm32": [ { "packageName": "libpng", "manifestSha256": "d21c80ef4dedf35828c1cc74d741abdafc003f7048cf474b2fdfd9143016bd76", - "cacheKey": "7b652e83e5ea6e2619e01d2cc13415258f5b87013bbf7e373a72b848b6b5bec9" + "cacheKey": "42ca6a45c189e22774e064cefc799d7790a9dc83ab06ccd41a6b7594e9ff8e93" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3889,7 +3958,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "wasm32": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, "dependencyClosures": { "wasm32": [] @@ -3910,7 +3979,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "wasm32": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, "dependencyClosures": { "wasm32": [] @@ -3931,14 +4000,14 @@ "wasm32" ], "cacheKeys": { - "wasm32": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "wasm32": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, "dependencyClosures": { "wasm32": [ { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" } ] }, @@ -3958,19 +4027,19 @@ "wasm32" ], "cacheKeys": { - "wasm32": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "wasm32": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, "dependencyClosures": { "wasm32": [ { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" } ] }, @@ -3990,239 +4059,239 @@ "wasm32" ], "cacheKeys": { - "wasm32": "18970d4c019d106a8c99af8e62e4db0bb3bc1d2c1a631f8be4ca29f856e996e3" + "wasm32": "420af5ddd540a93ae4fa87a205066b8417ffe60a558c82ee20b1438797eb863a" }, "dependencyClosures": { "wasm32": [ { "packageName": "bash", "manifestSha256": "2dd728f9834b4dcb62229e032e12d87b1428921d029d7d77b4f68c8debabba0f", - "cacheKey": "6f5dabe2d2610159ce080299c44cbd209be88dda2bd995a503d76c07ba257e54" + "cacheKey": "dbb123525faf0c3ae0c6f07d052d76b6287e8593b1150825280347da38916269" }, { "packageName": "bc", "manifestSha256": "5c44519f861efc263ab401b2ac95a5c20fdc1c4d945d8bff196400cfd93fa22b", - "cacheKey": "624b65f00e1971398ac04a007e37382b341123990dcded17d74ceb9a042b246d" + "cacheKey": "630496425f9c318d7bbd8477336af2df14551861dca7c657a9f95b0de2fe61e6" }, { "packageName": "bzip2", "manifestSha256": "bb06517207c2ceb40ccf9b04914d310c42017aae39aa803e8bd71b06724764b2", - "cacheKey": "9703581822273fda2f601c4f7e2829e09b05ba03237a8b97a9b9bd11d9b49778" + "cacheKey": "fefdb1bea01c7a2c26630276c6ea2a6de46c10c8bf394021e287223b1ed62d86" }, { "packageName": "coreutils", "manifestSha256": "7de40d1f4c454ead124f49ceb0f8da6a57f536bb367f8d6fec44b4976c3543d9", - "cacheKey": "19c4e83604a1f08775c68956a19fd019763045ce67439000fd84ab24b4565e24" + "cacheKey": "2443b7c15bb0f5ff3b3607004e55b3c232b59af1eeba5699bb8f72495990cd96" }, { "packageName": "curl", "manifestSha256": "4b42c4709ee0db6ae4ed02e342781debed91435c793e5a2a7b1e6b8ca724801a", - "cacheKey": "6a8f4f28617f51241f6fcfbb0b0941dbba1d734a1ad5b1a826f649a6812ae668" + "cacheKey": "da7d6f9e9ecd25f6dac100a8c1752582abf897b831511e86b1adcad7aab5cbe7" }, { "packageName": "dash", "manifestSha256": "f9ba4e11179cca17a8f9177d558391a48242468b579115aa8a4d949bfb69a875", - "cacheKey": "53ce7a3a80766fad54e87c3de65ded218c6ebf79eb84c73fdf1965f639a4d9d0" + "cacheKey": "5afda7833197534c939ed6f6575dfe30af595183976b30d9a113877a17b3d5cf" }, { "packageName": "diffutils", "manifestSha256": "81d1846004b3c669292e3d381b70cc0937c7afc59c139917acfee25a7b85f45f", - "cacheKey": "de768e358feda937be1ed513ee098f1cabbe338df2dd8b23e4806ce98a538985" + "cacheKey": "0cbda5e2e319fba08d0fab6e1237c112d8fcac3b0483f4cb62d787b67cc7091e" }, { "packageName": "dinit", "manifestSha256": "1de1650c6ca61a8b19787b354c7d093c7614014ea2bc88e5e1ee174f3008bef8", - "cacheKey": "efbe355335c492b649d8de161c4dc07026b8e9bcfd53acc310041a797dd339bf" + "cacheKey": "758506281588915ff814a618ad91277433c0dac2e9ad36af2112ab70169de255" }, { "packageName": "fbdoom", "manifestSha256": "2fee10c281849df0569c85ae082494ce4cecaf83257bb7b5ceb9c57974740cb8", - "cacheKey": "3d2056faa4f7f2dbae6255ff2e5120cd5c76cba01916c2365b1ac29f8500b229" + "cacheKey": "39ccfc9a7c5408ee49c760825676eb172684b65da83cfa77f63ba319b59cfc80" }, { "packageName": "file", "manifestSha256": "af9bdc22a415acdb621b5f5c266540aacb856953acb7d4c19ef7e575b0e83682", - "cacheKey": "659cae184c039ede37a3c0ae4f21ecee68ea4decf443227ff5155dca7904b1e1" + "cacheKey": "a03c7c73c2401cd0515cfb18037e4721b66a40ea6f28bfc04ceea1d0704e67c8" }, { "packageName": "findutils", "manifestSha256": "89c73153e867738697aaf7c382a766ef6d1b1abdd8c09c29dcde20212596960c", - "cacheKey": "ff3329505ccd07d15dfe5d049d0dadc4ae2ce81c2541eccc69c1f9c1060e7d9c" + "cacheKey": "e6420a15e8509fa0e6c0cd3880d784e789eadd0bc871cbad3736ea131d925906" }, { "packageName": "gawk", "manifestSha256": "a541a82d7c3049235c12c0bb19edf44e7e4a4954ddb5a5033c20d417d0e7c2a4", - "cacheKey": "cc9dbfdcfc74eb507d7495c200ee3ae532706711ecd62b70238dfede5f012d4d" + "cacheKey": "034c08992d9a9112c786495e4897f69c9a07b809a460a57d25e14763aceeb072" }, { "packageName": "git", "manifestSha256": "ad5c8e8612e7431e7c7b73e69d517564292bbc41f6408e5d5cb3190881097403", - "cacheKey": "732c9d08b431a75ec9bb5bd67530ea21a738f3c191269d8b2417ee27b0a4aebf" + "cacheKey": "da833f7fd84b54207c10fba98fbe460731e97ba61e1dbfee10a656e6011815c1" }, { "packageName": "grep", "manifestSha256": "82bea07002f3bd2d70b340849f0a8d5f9a479e67a59f5fdc5f5ed02a26ed8745", - "cacheKey": "b94bdcee325360c714a9af22cfbc3cb0ffb1356d039887dcd2500da8b58152fc" + "cacheKey": "dbb4316e6181020556cffdff08a9fabba357b003db4ce1c0cc4d1ba8fd9fd647" }, { "packageName": "gzip", "manifestSha256": "5d0056490a3edb52473814878025ca05c47ac525c25679fd8ae0378d75b6a831", - "cacheKey": "0f05172e0ae43d531e2730cffbdddb4de4706451bd2e5f57d4169b0797d63544" + "cacheKey": "77229f70955c1156a63e64c4ae8bc167456f2f75b9c6240098054070329ff5c7" }, { "packageName": "icu", "manifestSha256": "e1169851c1978d3356c4e4c0f396672cb6b7785375a44411f7211ddfc90e7ad5", - "cacheKey": "29b737564e6d227336ef5ab8853ea2a2bf18b6d01f4619e04252cb9a66aea401" + "cacheKey": "d3da6e8512e1edd9452bf2f7898756037a1074bbedf191912c6bbbb827ef245a" }, { "packageName": "kernel", "manifestSha256": "3cfdfa2130a0917b23ee9ae760901e5caa225654169a51f767413c38025b7b2d", - "cacheKey": "6f7364081f0971d3948eaf7c3f8b11b00b0ce98bc9d1525c75d03ef056f65d71" + "cacheKey": "db62f6bd1409ee6c7f465f21d796dc4717d63fa2bed8bb2c6e7d86cbc4c5b217" }, { "packageName": "less", "manifestSha256": "65a0b5507655e741acc125a27b694c8ada5ed643299fdf9b41afe790f3c84468", - "cacheKey": "459cb5f7ade58fb86b7d84e9f30c76c3abff3016d25d0ce0f1606ab6cac80da8" + "cacheKey": "f3a5c19d76e37d223018e566db381eef0ea04265151571e146eb7fded3293989" }, { "packageName": "libcurl", "manifestSha256": "d81c9d71f1eacc9f630f7774e0adbe6eb736a83d2f5271ea9814e9c672512b80", - "cacheKey": "354bf0d6a423b81dcd5d3847d6b505f57fb0615db4fb86a994b2142c5856fda1" + "cacheKey": "961e5b1c7a3fc50240846a3ef69a9fb55711225a8b55d0ba6de7d4019b5542f4" }, { "packageName": "libcxx", "manifestSha256": "7a2de391ea6ee39accafcd40e7b60c1d43ce4d82336f1064d935f738f063004f", - "cacheKey": "1d8f7ff496ad523216fe91ff741b2c99a629292e59cda8baa4dc46dce676de66" + "cacheKey": "b9a42eff6f300432a180f4917ec1f816e502db20765f3098f8ae61046626749a" }, { "packageName": "libiconv", "manifestSha256": "7ae4f68412db04f01e027f94b0d14c1130ac4dfb2041270207994ddab56f0855", - "cacheKey": "939a64271c7d51277a5ec7a49c45c55fb4c4b252c68361ca3c6beb6bf9a3111b" + "cacheKey": "2bc2df82f5537c5f74f7e06dc3e6375f4ef24a0866e54df0f2e75f0152baf538" }, { "packageName": "libxml2", "manifestSha256": "87301bfcb607ee24c00925787cfe071ba506c750437ff5b807b2ea0897058e91", - "cacheKey": "0f93f0ef28cebb24d87516d0db6683a8909359a10f1577db92a8374b04ae7cdd" + "cacheKey": "a90b8526a47a799b2917d4dc3c6d16f1d65ece66005e484a322c503e5c5805d8" }, { "packageName": "libzip", "manifestSha256": "84a6e61fc67c7dd6cf5b4aacd80682a86d402c78810efdd03a9ff58da8eaf6a0", - "cacheKey": "f7b217af4f83eeb207d7cbfca570144719a67f4df6ae3c1fc56a8694dd0568ce" + "cacheKey": "7ddffc7b50eea7a8ecebc94f88c1f6e6953b1d1bfd0bb30bb6f33805c3f100da" }, { "packageName": "lsof", "manifestSha256": "f2c156121bbc14891ed642cd4d0e0f8b6695861023882a16a4d086f6b0d650d9", - "cacheKey": "2e059b4fc2aa0f9972fcfb2ebc60fa93d58317b6b9310377d717ca0d59c335d8" + "cacheKey": "013dde10d8ef51c9f703d79d3a05d6b1ec9c19bf66f94d127ee2154a030c2760" }, { "packageName": "m4", "manifestSha256": "919dd8f79a58c8fa7ff55a13003dd38c3f07d9c8cd150213fb4d855fc7269e9b", - "cacheKey": "2bb4f2d522a1f16c6da830c058ebbba808ddc6c5f065fa2d789a62d081495695" + "cacheKey": "9aaa483203deb8062749281074335885baeb6dd38ea4ae0a14647c8a73377f3d" }, { "packageName": "make", "manifestSha256": "c01bf3f64968c041bba5d18950c08a854d5c681ea7fd883856408cd6f1409a63", - "cacheKey": "c877adfa51c778e4f579f751a4b4695d9185bb3f6e23ede0ca91128ece524bd5" + "cacheKey": "d94425e48a77368549234e42055f277835ea1c01c8d9316ec5137173242fcbdb" }, { "packageName": "modeset", "manifestSha256": "7f3e362369709c3699963efc55864f39ef8cff6f8a1bf57a8053029fb91534b7", - "cacheKey": "ec3e48fde1c602afd114f6e43a48eb4267c2283a05cd5635c7a178abab59fb60" + "cacheKey": "bc9716a91a63bd1c17dd27d15ddd8b80fcbaab9380fc7e33812b3737865e45d0" }, { "packageName": "msmtpd", "manifestSha256": "eb554efa7f11b5d2c2268492eecf35bc0d80eeebbcffc1441b590b5541b4ec16", - "cacheKey": "9a013fcbb76a91f84bcf10962e77fb9f79ab66ad8ab9cc066d1053a92aa6a4a7" + "cacheKey": "7c8c1a37ebb098b5648349ac9d1600b212ace546c6d8ef87258e6796d78e0be1" }, { "packageName": "nano", "manifestSha256": "f9c694eef9e6136e8b0f30904d720ffc75c7cd03cb80ffe8c08d1ba5911535ae", - "cacheKey": "0556a9e66f11d29a51f75ca564a90ee9500489292ab7b36683fd8d8ed8da3086" + "cacheKey": "8ccc600f7d195eea56d8297f95983d0eeebe347821027876cba8aefca48de1ce" }, { "packageName": "ncurses", "manifestSha256": "633681b4495fbcb0522dfc9a15ce9aec76ff85bbcdfed9767efbb3931877f194", - "cacheKey": "79998bb8ec023af459faadccd3a5f3efa9779cad66fdfa8d5431e5ac94608c97" + "cacheKey": "3bf70a3343176529778e131d396392192503d81622424bd525c3f3be3cfbdfa4" }, { "packageName": "netcat", "manifestSha256": "b54857de3d16117aaba37168a01ceb9d7f9ea2b8b00537aa7ecd6b6207fcf63f", - "cacheKey": "c56c7df2a9afc4ffb5ca5e141080e52b611eddd4e7c386a0bd478e97fe0b1b28" + "cacheKey": "bc50624baba901d1d799ee8f5e1162d16e54e743c7aaec162c9c68ffdd01135a" }, { "packageName": "nethack", "manifestSha256": "fc65d8274f946089c178c42092e29cec558ead0a82e339fa85fc5dad5d992dd8", - "cacheKey": "ebe7bf355ad4dfe61391f106a2d2096a632c984c2f37f10a1fcd4af6dae8ba2d" + "cacheKey": "921e37b24dc9b441523548398566bfa090997af77feba3f84ef6bab6c2f39e12" }, { "packageName": "nethack-browser-bundle", "manifestSha256": "054104dd336691407a7c06a437fdd2cfc7e15036dd64800912b97d445a3ce73a", - "cacheKey": "dee7f5bffd3b39600ac150e1e2f8315c455b3fb037fe450dc96acacd224d5487" + "cacheKey": "044e9e1933d3cc379886cd7f0328f1319b997a33649397fe6d98af3d9ef220b6" }, { "packageName": "nginx", "manifestSha256": "b78552502c63bef2814c8aae76fedfdb4f273e4ee0c42023d6157ddb298ff815", - "cacheKey": "592477087290da56bd3b002bed30d2cbe23245871bea5c4734070bdd161ad640" + "cacheKey": "35614805625547ec02399b25562f922cebc0e2c639703c07ad50f4ac9fc41d5a" }, { "packageName": "openssl", "manifestSha256": "865dc8ce9577ce1cc7af69d85c063336a5b586a28b759aa926b3aa98c0fd6e19", - "cacheKey": "eaab6c0fc5d1c5b493e4484bbdfe0f3cd72b76fce5d2de926191cb3fc8824246" + "cacheKey": "0aec27fe558e0ea39004e05971fb622869504a2070991ab1dc3fd3d579faccec" }, { "packageName": "php", "manifestSha256": "14114e280941f1f2f6a4c5abc11698b4b01f909be4cf2aac5b0e68845fed4644", - "cacheKey": "6062638f2f20a7254948ee6bede9c4a1515d0ff4565abb15084b037c3510ab05" + "cacheKey": "70a85fce082656d22b33c356285b2659a00ff59e3efeda9f35862e8fdd6c0abd" }, { "packageName": "posix-utils-lite", "manifestSha256": "988198a2ed062a1c292f2f55373eb213a8ba20c2ad0ab377fc46d82cbb610fc9", - "cacheKey": "4e1f08e7289352b70e10055a9aa128a1f55ffbb64c71ec1d399d39e0647dedf9" + "cacheKey": "786cca62acce051524fc3d07167d7416d9804cef413b2b22aaa425e083e138c2" }, { "packageName": "rootfs", "manifestSha256": "fd79258e3fd38a31b2550fd156fd53e460669db5fc47f34f3e9ac654c545d7dc", - "cacheKey": "ccb9ae9c4ad17b7ce6eb87066c0379f5abb4da6ac1f4fb879793c7abce5331f1" + "cacheKey": "824f2fd46dc0a9771d8aba1b4edbd146490093b97db7b126d884df8e20a1bcc8" }, { "packageName": "sed", "manifestSha256": "0ac72c93e26ffe357cb6efe39ff8b20c9093023a839d9e48e25bd90ec64e55b3", - "cacheKey": "7bd877086d79a9de826184c951847a26cbe3a588721fe1731aa0808aecf4a024" + "cacheKey": "caf574024999578d4daf8a5229d5d5fbeff6e86a53d34c3c66e7fb14e2d11be9" }, { "packageName": "shell", "manifestSha256": "cafd0902b76845067dd291c16cad2730fc2a12b6d2ceb9774734509f6dbc95ed", - "cacheKey": "03ad86067626316dd2a54b3c0375242cc59c934345f45f67dc51428cada8dcca" + "cacheKey": "87ef333a1ace9c3f490eb4013f01015148c3d6b3678661281cee727f0780752e" }, { "packageName": "sqlite", "manifestSha256": "77110d20137fe7dfd55d6be0f99c959ec3d4bc23ebbf516e5eb51f3886d8bcee", - "cacheKey": "225473952aaa1f400b89870afc6943881cbbbbbe96019b5b7fd47e913a29fcb2" + "cacheKey": "fce7d13e41a79094256bc45771a13581e2c699dead5a29841561c76d435425c6" }, { "packageName": "tar", "manifestSha256": "f81edb1068a9c85e9c9ed54fee800dad824ebbf431695f7f56c501da9a457ba1", - "cacheKey": "38058bb49eca256b350b86a28e5bfd8e603e809fdc866aecc479ae440235d068" + "cacheKey": "c0f8fa2a1f81fdc49baef2545d4d99ac2a606410013c91497025520f908a8923" }, { "packageName": "unzip", "manifestSha256": "05b365fd79288e6f3dc0902ee5b26896eac3ae8c9df07f41fa86c7b9591c2240", - "cacheKey": "b5cf139d9b2bf064a73f6d7c5af1893f22435da2a74bea3a5775a11188419033" + "cacheKey": "4f988be92f7df98e01b8189da167c97b65f2051052e46d2e8d5284f7746e2f85" }, { "packageName": "vim", "manifestSha256": "b3641cb49f2fd59057cc63e8d62424de26cd0eb72ae5847ea7867964e601f86c", - "cacheKey": "2bd24d9a584a23a56de78d4f5cc79d6373c9391f7114a7a6d4393b87fad085aa" + "cacheKey": "c992bf5f62eebd9bd62044874b3b389819edd3876c3cd91acc2b9f9062cec3ea" }, { "packageName": "vim-browser-bundle", "manifestSha256": "c70153f22f254a772c53fe58aeb85d1777f51b856541e417f92174a211b1fb7f", - "cacheKey": "499b12428415f585682af4044919d45d4bf4dc01801d83a555875c86d98b3b51" + "cacheKey": "f87795e98217f2bbdf7335e7c168f8acef7f8c752646ff2732fb2be30f653a61" }, { "packageName": "wget", "manifestSha256": "36f273b9ca7a938777f6d51256d8210d6cea7bb4ff74f085c3ee43b06f867d9f", - "cacheKey": "ca0f0449df40c212ca6b482f4c2aa8d0ee8a8b63c49fdc90d90f64b268888142" + "cacheKey": "7bdb053e72627a7516ed3216ce7ec6aa60bde6991fea1180572a85e6688fe6d1" }, { "packageName": "wordpress-sqlite-integration-source", @@ -4232,22 +4301,22 @@ { "packageName": "xz", "manifestSha256": "22bf03414f0abc7eb80c8aa4cce61b08386116ad6ef3f9fe27d985b53ca996ce", - "cacheKey": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "cacheKey": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, { "packageName": "zip", "manifestSha256": "86546152e545f1cc5c1579074fea0c938e8e59c83119fa11460c202970da7742", - "cacheKey": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "cacheKey": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, { "packageName": "zlib", "manifestSha256": "95773710b08f492b4dc83cb7dd3d9a18fda901ced19b4936be330ba5a6734983", - "cacheKey": "f4880c5db1724d8c999ba19965dbf9cb21948a66355d88430603f48c7d85c5a6" + "cacheKey": "b71989d458b4b6b460553292b64f1ce5eff74952212c600b3fa07db0c81361ce" }, { "packageName": "zstd", "manifestSha256": "848833483664496f204f9d66c65a403e0578d34cf45c4b00590b09956a355036", - "cacheKey": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "cacheKey": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" } ] }, @@ -4267,7 +4336,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "24bc0eefa6026efa694b610279056d2eea703093d477ffc2e618b19b315c2450" + "wasm32": "3068f7ee29969ae70a05db6c9bbdabba080a2a84ab9c05273a961e1f88c29d8c" }, "dependencyClosures": { "wasm32": [] @@ -4288,7 +4357,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "3cd126749fb97d0dd1d3616b72027c416c44ddfb48a9e372b2657beea2e6f862" + "wasm32": "7ecdbe10d64115f05753396dab34e3f97beb5d9b6b9948ee3a9c1bcf2076bab8" }, "dependencyClosures": { "wasm32": [] @@ -4309,7 +4378,7 @@ "wasm32" ], "cacheKeys": { - "wasm32": "3fe56fe859bd4fa407b8ea69e8ac1e68e66de1724fa98a092b51769075870cec" + "wasm32": "00b999f693a06a1444022d375067db898a296d1efc03684d39d9489bb8632c89" }, "dependencyClosures": { "wasm32": [] diff --git a/packages/sets/local-supported.toml b/packages/sets/local-supported.toml index b6170d16ab..d6c6c1a3e8 100644 --- a/packages/sets/local-supported.toml +++ b/packages/sets/local-supported.toml @@ -66,6 +66,14 @@ class = "user-software" name = "dinit" class = "user-software" +[[packages]] +name = "espeak-ng" +class = "user-software" + +[[packages]] +name = "evdev-demo" +class = "user-software" + [[packages]] name = "fbdoom" class = "user-software" diff --git a/programs/evdev_demo.c b/programs/evdev_demo.c new file mode 100644 index 0000000000..95dc368429 --- /dev/null +++ b/programs/evdev_demo.c @@ -0,0 +1,88 @@ +/* + * evdev_demo — C1 compile proof and runtime backing for `/?demo=evdev`. + * + * Only in-tree consumer of ``: the _Static_assert below + * fires at build time if the vendored header drifts away from the + * kernel-side WpkInputEvent layout. input-evdev-smoke.c stays inline + * on purpose so the B5 fixture's ABI check is independent of C1. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +_Static_assert(sizeof(struct input_event) == 24, + "struct input_event must be 24 bytes on wasm32 (musl 64-bit time_t)"); + +#define EV_BATCH 16 + +static void log_kbd(const struct input_event *e) { + if (e->type == EV_KEY) { + const char *state = e->value == 0 ? "up" + : e->value == 1 ? "down" + : "repeat"; + printf("key %s: code=%u\n", state, (unsigned) e->code); + fflush(stdout); + } +} + +static void log_ptr(const struct input_event *e) { + if (e->type == EV_REL) { + printf("ptr rel code=%u value=%d\n", + (unsigned) e->code, (int) e->value); + fflush(stdout); + } else if (e->type == EV_ABS) { + printf("ptr abs code=%u value=%d\n", + (unsigned) e->code, (int) e->value); + fflush(stdout); + } +} + +int main(void) { + int kbd = open("/dev/input/event0", O_RDONLY | O_CLOEXEC); + if (kbd < 0) { perror("open /dev/input/event0"); return 1; } + int ptr = open("/dev/input/event1", O_RDONLY | O_CLOEXEC); + if (ptr < 0) { perror("open /dev/input/event1"); return 1; } + + char name[64] = {0}; + if (ioctl(kbd, EVIOCGNAME(sizeof name), name) < 0) { + perror("EVIOCGNAME event0"); return 1; + } + printf("kbd: %s\n", name); + if (ioctl(ptr, EVIOCGNAME(sizeof name), name) < 0) { + perror("EVIOCGNAME event1"); return 1; + } + printf("ptr: %s\n", name); + printf("ready: type or move the mouse over the canvas\n"); + fflush(stdout); + + struct pollfd pfds[2] = { + { .fd = kbd, .events = POLLIN }, + { .fd = ptr, .events = POLLIN }, + }; + + for (;;) { + int n = poll(pfds, 2, -1); + if (n < 0) { + if (errno == EINTR) continue; + perror("poll"); return 1; + } + struct input_event evs[EV_BATCH]; + if (pfds[0].revents & POLLIN) { + ssize_t r = read(kbd, evs, sizeof evs); + for (ssize_t i = 0; i < r / (ssize_t) sizeof(evs[0]); i++) { + log_kbd(&evs[i]); + } + } + if (pfds[1].revents & POLLIN) { + ssize_t r = read(ptr, evs, sizeof evs); + for (ssize_t i = 0; i < r / (ssize_t) sizeof(evs[0]); i++) { + log_ptr(&evs[i]); + } + } + } +} diff --git a/programs/input-evdev-smoke.c b/programs/input-evdev-smoke.c new file mode 100644 index 0000000000..5555619636 --- /dev/null +++ b/programs/input-evdev-smoke.c @@ -0,0 +1,130 @@ +/* + * Three-phase fixture driven by host/test/input-evdev.test.ts. Structs + * and ioctl numbers are inlined (not ) so the B5 ABI + * check stays independent of the C1 sysroot header land. + */ +#include +#include +#include +#include +#include +#include + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define SYN_REPORT 0x00 +#define SYN_DROPPED 0x03 + +#define EVIOC_DIR_READ (2u << 30) +#define EVIOC_MAGIC (0x45u << 8) /* 'E' */ +#define EVIOCGNAME(len) (EVIOC_DIR_READ | (((unsigned)(len) & 0x3fffu) << 16) | EVIOC_MAGIC | 0x06u) +#define EVIOCGABS(axis) (EVIOC_DIR_READ | ((24u) << 16) | EVIOC_MAGIC | (0x40u + ((unsigned)(axis) & 0x3fu))) + +struct wpk_event { + int64_t tv_sec; + int32_t tv_usec; + int32_t _pad; + uint16_t ev_type; + uint16_t code; + int32_t value; +}; + +struct wpk_absinfo { + int32_t value, minimum, maximum, fuzz, flat, resolution; +}; + +_Static_assert(sizeof(struct wpk_event) == 24, "WpkInputEvent must be 24 bytes"); +_Static_assert(sizeof(struct wpk_absinfo) == 24, "WpkInputAbsinfo must be 24 bytes"); + +static void wait_sync(void) { + char c; + while (read(0, &c, 1) <= 0) { } +} + +static void print_event(const char *tag, int idx, const struct wpk_event *e) { + printf("%s_ev%d type=%u code=%u value=%d tv_sec=%lld tv_usec=%d\n", + tag, idx, (unsigned)e->ev_type, (unsigned)e->code, (int)e->value, + (long long)e->tv_sec, (int)e->tv_usec); +} + +int main(void) { + /* --- Phase 1: keyboard (event0) ----------------------------------- */ + int fd0 = open("/dev/input/event0", O_RDONLY); + if (fd0 < 0) { perror("open event0"); return 1; } + + char name0[64] = {0}; + if (ioctl(fd0, EVIOCGNAME(sizeof(name0)), name0) < 0) { + perror("EVIOCGNAME event0"); return 1; + } + printf("kbd_name=%s\n", name0); + printf("READY:kbd\n"); + fflush(stdout); + wait_sync(); + + char buf0[48]; + ssize_t n0 = read(fd0, buf0, sizeof(buf0)); + if (n0 != 48) { fprintf(stderr, "kbd read returned %zd\n", n0); return 1; } + struct wpk_event ke0, ke1; + memcpy(&ke0, buf0, sizeof(ke0)); + memcpy(&ke1, buf0 + 24, sizeof(ke1)); + print_event("kbd", 0, &ke0); + print_event("kbd", 1, &ke1); + fflush(stdout); + + /* --- Phase 2: pointer (event1) ------------------------------------ */ + int fd1 = open("/dev/input/event1", O_RDONLY); + if (fd1 < 0) { perror("open event1"); return 1; } + + struct wpk_absinfo abs_x; + if (ioctl(fd1, EVIOCGABS(0 /* ABS_X */), &abs_x) < 0) { + perror("EVIOCGABS ABS_X"); return 1; + } + printf("ptr_abs_x_max=%d\n", (int)abs_x.maximum); + printf("READY:ptr\n"); + fflush(stdout); + wait_sync(); + + char buf1[48]; + ssize_t n1 = read(fd1, buf1, sizeof(buf1)); + if (n1 != 48) { fprintf(stderr, "ptr read returned %zd\n", n1); return 1; } + struct wpk_event pe0, pe1; + memcpy(&pe0, buf1, sizeof(pe0)); + memcpy(&pe1, buf1 + 24, sizeof(pe1)); + print_event("ptr", 0, &pe0); + print_event("ptr", 1, &pe1); + fflush(stdout); + + /* --- Phase 3: ring overflow on event0 ----------------------------- */ + printf("READY:overflow\n"); + fflush(stdout); + wait_sync(); + + /* Blocking read on an empty+clean ring returns 0 — drain terminator. */ + int count = 0, syn_dropped_at = -1, non_syn_dropped = 0; + struct wpk_event last = {0}; + for (;;) { + char rec[24]; + ssize_t n = read(fd0, rec, sizeof(rec)); + if (n == 0) break; + if (n != 24) { fprintf(stderr, "drain short read %zd\n", n); return 1; } + struct wpk_event ev; + memcpy(&ev, rec, sizeof(ev)); + if (ev.ev_type == EV_SYN && ev.code == SYN_DROPPED) { + if (syn_dropped_at < 0) syn_dropped_at = count; + } else { + non_syn_dropped++; + } + last = ev; + count++; + if (count > 1500) { fprintf(stderr, "drain runaway\n"); return 1; } + } + printf("ov_count=%d ov_syn_dropped_at=%d ov_real=%d ov_last_type=%u ov_last_code=%u\n", + count, syn_dropped_at, non_syn_dropped, + (unsigned)last.ev_type, (unsigned)last.code); + fflush(stdout); + + close(fd0); + close(fd1); + return 0; +} diff --git a/tests/package-system/espeak-ng-package.test.ts b/tests/package-system/espeak-ng-package.test.ts new file mode 100644 index 0000000000..a80d78c0aa --- /dev/null +++ b/tests/package-system/espeak-ng-package.test.ts @@ -0,0 +1,109 @@ +import { existsSync, readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +const repoRoot = resolve(import.meta.dirname, "../.."); + +function source(path: string): string { + return readFileSync(join(repoRoot, path), "utf8"); +} + +describe("espeak-ng package contract", () => { + it("pins both upstream archives by version and digest", () => { + const manifest = source("packages/registry/espeak-ng/package.toml"); + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(manifest).toContain('version = "1.52.0"'); + expect(manifest).toContain( + 'sha256 = "bb4338102ff3b49a81423da8a1a158b420124b055b60fa76cfb4b18677130a23"', + ); + expect(build).toContain('PCAUDIO_VERSION="1.3"'); + expect(build).toContain( + 'PCAUDIO_SOURCE_SHA256="e8bd15f460ea171ccd0769ea432e188532a7fb27fa73ec2d526088a082abaaad"', + ); + expect(build).toContain("shasum -a 256 -c -"); + }); + + it("selects the OSS backend without patching either source tree", () => { + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect( + existsSync(join(repoRoot, "packages/registry/espeak-ng/patches")), + ).toBe(false); + expect(build).not.toMatch(/^\s*patch\b/m); + expect(build).toContain("#define HAVE_SYS_SOUNDCARD_H 1"); + expect(build).not.toContain("HAVE_ALSA"); + expect(build).not.toContain("HAVE_PULSEAUDIO"); + expect(build).not.toContain("audio_kandelo"); + }); + + it("generates the shipped data dir from the native tree, never the wasm one", () => { + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(build).toContain( + 'cmake --build "$NATIVE_BUILD_DIR" --target data', + ); + expect(build).not.toContain( + 'cmake --build "$CROSS_BUILD_DIR" --target data', + ); + expect(build).toContain('cp -R "$NATIVE_BUILD_DIR/espeak-ng-data"'); + }); + + it("declares every host tool the source build invokes", () => { + const manifest = source("packages/registry/espeak-ng/package.toml"); + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + for (const tool of ["cmake", "curl", "tar", "shasum", "python3"]) { + expect(manifest).toContain(`name = "${tool}"`); + } + expect(build).toContain("for tool in cmake curl tar shasum python3; do"); + expect(manifest).toContain('version_constraint = ">=7.71.0"'); + }); + + it("resolves libcxx, which upstream builds unconditionally for speechPlayer", () => { + const manifest = source("packages/registry/espeak-ng/package.toml"); + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(manifest).toContain('depends_on = ["libcxx@21.1.7"]'); + expect(build).toContain("build-deps --arch=wasm32 resolve libcxx"); + }); + + it("builds through the worktree SDK and publishes one wasm output", () => { + const manifest = source("packages/registry/espeak-ng/package.toml"); + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(build).toContain('source "$REPO_ROOT/sdk/activate.sh"'); + expect(build).toContain("WASM_POSIX_DEP_OUT_DIR"); + expect(manifest).toContain('wasm = "espeak-ng.wasm"'); + }); + + it("publishes the voice data as a runtime file, never from the build tree", () => { + const manifest = source("packages/registry/espeak-ng/package.toml"); + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(manifest).toContain('artifact = "espeak-ng-data.zip"'); + expect(manifest).toContain( + 'guest_path = "/usr/share/espeak-ng/espeak-ng-data.zip"', + ); + expect(build).toContain("install_local_runtime_file espeak-ng"); + + // The projection moves a multi-member package under its own directory, + // so consumers must name the closure paths rather than the flat ones. + const projection = JSON.parse( + source("packages/registry/program-packages.json"), + ) as { + packages: Record }>; + }; + expect( + projection.packages["espeak-ng"]?.members.map((m) => m.mirrorPath), + ).toEqual(["espeak-ng/espeak-ng.wasm", "espeak-ng/espeak-ng-data.zip"]); + }); + + it("builds the archive deterministically so the cache key follows the data", () => { + const build = source("packages/registry/espeak-ng/build-espeak-ng.sh"); + + expect(build).toContain("compression=zipfile.ZIP_STORED"); + expect(build).toContain("timestamp = (1980, 1, 1, 0, 0, 0)"); + expect(build).toContain("key=lambda item: item.as_posix()"); + }); +}); diff --git a/tools/xtask/src/dump_abi.rs b/tools/xtask/src/dump_abi.rs index c2a1ec9579..2c5ac60e1e 100644 --- a/tools/xtask/src/dump_abi.rs +++ b/tools/xtask/src/dump_abi.rs @@ -3414,6 +3414,35 @@ fn render_ts_module() -> String { } out.push_str("};\n\n"); + out.push_str("export interface IoctlRequestFamily {\n"); + out.push_str(" dir: number;\n"); + out.push_str(" magic: number;\n"); + out.push_str(" nrFirst: number;\n"); + out.push_str(" nrLast: number;\n"); + out.push_str(" direction: IoctlDirection;\n"); + out.push_str(" fixedSize: number | null;\n"); + out.push_str(" maxCallerSize: number | null;\n"); + out.push_str("}\n\n"); + out.push_str("export const IOCTL_REQUEST_FAMILIES: IoctlRequestFamily[] = [\n"); + for family in shared::ioctl_contract::IOCTL_REQUEST_FAMILIES { + let (fixed_size, max_caller_size) = match family.size { + shared::ioctl_contract::IoctlFamilySize::Fixed(size) => (Some(size), None), + shared::ioctl_contract::IoctlFamilySize::CallerEncoded { max } => (None, Some(max)), + }; + out.push_str(&format!( + " {{ dir: {}, magic: {}, nrFirst: {}, nrLast: {}, direction: {:?}, \ +fixedSize: {}, maxCallerSize: {} }},\n", + family.dir, + family.magic, + family.nr_first, + family.nr_last, + ioctl_direction_name(family.direction), + ts_optional_u32(fixed_size), + ts_optional_u32(max_caller_size), + )); + } + out.push_str("];\n\n"); + out.push_str("export const SYSCALL_ARGS: Record = {\n"); for entry in shared::host_abi::SYSCALL_ARG_DESCRIPTORS { out.push_str(&format!(" {}: [\n", entry.syscall_number)); @@ -3801,6 +3830,7 @@ fn build_snapshot(kernel_wasm: &std::path::Path) -> Result { root.insert("host_adapter".into(), host_adapter()); root.insert("syscall_arg_descriptors".into(), syscall_arg_descriptors()); root.insert("ioctl_request_contracts".into(), ioctl_request_contracts()); + root.insert("ioctl_request_families".into(), ioctl_request_families()); root.insert("channel_status_codes".into(), channel_status_codes()); root.insert("process_native_layouts".into(), process_native_layouts()); root.insert("process_memory_layout".into(), process_memory_layout()); @@ -5471,6 +5501,35 @@ fn ioctl_request_contracts() -> Value { Value::Object(contracts.into_iter().collect()) } +fn ioctl_request_families() -> Value { + let families = shared::ioctl_contract::IOCTL_REQUEST_FAMILIES + .iter() + .map(|family| { + let (fixed_size, max_caller_size) = match family.size { + shared::ioctl_contract::IoctlFamilySize::Fixed(size) => { + (Some(size), None) + } + shared::ioctl_contract::IoctlFamilySize::CallerEncoded { max } => { + (None, Some(max)) + } + }; + let mut value: JsonMap = BTreeMap::new(); + value.insert("dir".into(), json!(family.dir)); + value.insert("magic".into(), json!(family.magic)); + value.insert("nrFirst".into(), json!(family.nr_first)); + value.insert("nrLast".into(), json!(family.nr_last)); + value.insert( + "direction".into(), + json!(ioctl_direction_name(family.direction)), + ); + value.insert("fixedSize".into(), json!(fixed_size)); + value.insert("maxCallerSize".into(), json!(max_caller_size)); + Value::Object(value.into_iter().collect()) + }) + .collect(); + Value::Array(families) +} + fn host_adapter() -> Value { let manifest = shared::abi::HOST_ADAPTER_MANIFEST; @@ -6954,6 +7013,13 @@ fn classify_compat_change(old: &Value, new: &Value) -> Result { classify_additive_object_by_key(key, old_value, new_value, &mut report)? } + // A request number absent from the table resolved to "unknown" + // before, so adding one cannot change how an older program + // marshals any call it already made. Changing or removing an + // entry would restage a different buffer size and stays breaking. + "ioctl_request_contracts" => { + classify_additive_object_by_key(key, old_value, new_value, &mut report)? + } "vfs_metadata" => { classify_additive_object_by_key(key, old_value, new_value, &mut report)? } @@ -6972,7 +7038,11 @@ fn classify_compat_change(old: &Value, new: &Value) -> Result bool { matches!( section, - "host_adapter" | "io_multiplexing" | "syscall_arg_descriptors" | "vfs_metadata" + "host_adapter" + | "io_multiplexing" + | "ioctl_request_families" + | "syscall_arg_descriptors" + | "vfs_metadata" ) } @@ -8273,6 +8343,120 @@ mod tests { ); } + fn snapshot_with_one_ioctl_contract() -> Value { + let mut snapshot = base_snapshot(); + snapshot.as_object_mut().unwrap().insert( + "ioctl_request_contracts".into(), + json!({ + "1074021776": { + "argKind": "scalar-i32", + "direction": "none", + "wasm32Size": 0, + "wasm64Size": 0 + } + }), + ); + snapshot + } + + #[test] + fn adding_an_ioctl_request_contract_entry_is_compatible() { + let old = snapshot_with_one_ioctl_contract(); + let mut new = snapshot_with_one_ioctl_contract(); + new["ioctl_request_contracts"]["2147763457"] = json!({ + "argKind": "pointer", + "direction": "out", + "wasm32Size": 4, + "wasm64Size": 4 + }); + + let report = classify_compat_change(&old, &new).unwrap(); + assert!(report.breaking.is_empty(), "{report:?}"); + assert_eq!( + report.additive, + vec!["added ioctl_request_contracts entry \"2147763457\""] + ); + } + + #[test] + fn changing_or_removing_an_ioctl_request_contract_entry_is_breaking() { + let old = snapshot_with_one_ioctl_contract(); + let mut resized = snapshot_with_one_ioctl_contract(); + resized["ioctl_request_contracts"]["1074021776"]["wasm32Size"] = json!(4); + + let report = classify_compat_change(&old, &resized).unwrap(); + assert_eq!( + report.breaking, + vec!["changed ioctl_request_contracts entry \"1074021776\""] + ); + + let mut dropped = snapshot_with_one_ioctl_contract(); + dropped["ioctl_request_contracts"] + .as_object_mut() + .unwrap() + .remove("1074021776"); + + let report = classify_compat_change(&old, &dropped).unwrap(); + assert_eq!( + report.breaking, + vec!["removed ioctl_request_contracts entry \"1074021776\""] + ); + } + + #[test] + fn adding_the_ioctl_request_families_section_is_compatible() { + let old = base_snapshot(); + let mut new = base_snapshot(); + new.as_object_mut().unwrap().insert( + "ioctl_request_families".into(), + json!([{ + "dir": 2, + "magic": 69, + "nrFirst": 64, + "nrLast": 127, + "direction": "out", + "fixedSize": 24, + "maxCallerSize": null + }]), + ); + + let report = classify_compat_change(&old, &new).unwrap(); + assert!(report.breaking.is_empty(), "{report:?}"); + assert_eq!( + report.additive, + vec!["added top-level section \"ioctl_request_families\""] + ); + } + + #[test] + fn narrowing_an_existing_ioctl_request_family_is_breaking() { + let family = |nr_last: u32| { + json!([{ + "dir": 2, + "magic": 69, + "nrFirst": 64, + "nrLast": nr_last, + "direction": "out", + "fixedSize": 24, + "maxCallerSize": null + }]) + }; + let mut old = base_snapshot(); + old.as_object_mut() + .unwrap() + .insert("ioctl_request_families".into(), family(127)); + let mut new = base_snapshot(); + new.as_object_mut() + .unwrap() + .insert("ioctl_request_families".into(), family(96)); + + let report = classify_compat_change(&old, &new).unwrap(); + assert_eq!( + report.breaking, + vec!["changed top-level section \"ioctl_request_families\""] + ); + } + #[test] fn adding_io_multiplexing_section_is_compatible() { let mut old = base_snapshot(); diff --git a/tools/xtask/src/local_build.rs b/tools/xtask/src/local_build.rs index 42578dc37a..2bdade81aa 100644 --- a/tools/xtask/src/local_build.rs +++ b/tools/xtask/src/local_build.rs @@ -3555,7 +3555,7 @@ mod tests { ); assert_eq!(first.schema, 1); assert_eq!(first.policy, "source-only-v1"); - assert_eq!(first.packages.len(), 72); + assert_eq!(first.packages.len(), 74); assert_eq!(first.products.len(), 7); assert_eq!( first @@ -3563,7 +3563,8 @@ mod tests { .iter() .map(|package| package.name.as_str()) .collect::>(), - "bash bc bzip2 coreutils cpython curl dash diffutils dinit fbdoom file \ + "bash bc bzip2 coreutils cpython curl dash diffutils dinit espeak-ng \ + evdev-demo fbdoom file \ findutils gawk git grep gzip icu kandelo-sdk kernel lamp less libcurl libcxx \ libiconv libpng libxml2 libzip lsof m4 make mariadb mariadb-test modeset \ msmtpd nano ncurses netcat nethack nethack-browser-bundle nginx nginx-php-vfs \ @@ -3586,7 +3587,7 @@ mod tests { ("browser-product", 8), ("platform", 16), ("test-support", 3), - ("user-software", 45), + ("user-software", 47), ]), ); for (class, expected) in [ @@ -5048,14 +5049,14 @@ materialization = "lazy" assert!(source.provider_was_explicit, "{name} must be explicit"); *providers.entry(source.provider.as_str()).or_default() += 1; } - assert_eq!(set.packages.len(), 72); + assert_eq!(set.packages.len(), 74); assert_eq!( set.dependency_only, ["pcre2-source", "wordpress-sqlite-integration-source"], ); assert_eq!( providers, - BTreeMap::from([("archive", 57), ("dev-shell", 1), ("repository", 16)]), + BTreeMap::from([("archive", 58), ("dev-shell", 1), ("repository", 17)]), ); }