Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c89a56e
feat(nix): the Qt 6 host toolchain — one QT_HOST_PATH prefix, with th…
mho22 Sep 1, 2026
66fd365
feat(packages): Qt 6.10.2 qtbase — QtCore, QtGui, and the Wayland pla…
mho22 Sep 1, 2026
a333f90
feat(packages): libdrm and wayland-protocols stage the pkg-config sur…
mho22 Sep 1, 2026
af27059
fix(kernel): a MAP_FIXED map inside a mapping carves, not evicts
mho22 Sep 1, 2026
8913238
feat(packages): QtQml runs — qtshadertools, qtdeclarative, and a QML …
mho22 Sep 1, 2026
819f7ec
fix(xtask): build-deps answers and builds under the active resolve po…
mho22 Sep 1, 2026
1f65ba5
fix(host): spawn resolvers return bytes only — the candidate compiles…
mho22 Sep 1, 2026
13155a0
feat(packages): qtdemo — a QRasterWindow client and its smoke gate
mho22 Sep 1, 2026
dd536d9
feat(packages): Quickshell 0.3.1 and cli11 on wasm32
mho22 Sep 1, 2026
b24912d
feat(dri/omarchy): Qt Demo and Quickshell join the desktop
mho22 Sep 1, 2026
922c83e
fix(examples): perl resolves through its package directory path
mho22 Sep 1, 2026
4d57c29
fix(dri): the modeset fixture polls before drmHandleEvent
mho22 Sep 1, 2026
076a470
test(packages): the shell bridge follows revision 32 and the terminfo…
mho22 Sep 1, 2026
fee052c
feat(dri/omarchy): the theme gallery replaces qtdemo — click a card, …
mho22 Sep 2, 2026
698dfb6
feat(fork-instrument): emitted code shrinks to what a landing actuall…
mho22 Sep 3, 2026
d5e4842
fix(kernel): the inotify family fails with ENOSYS instead of faking s…
mho22 Sep 3, 2026
917807e
build(qt): the browser's per-worker compile cost bounds the Qt stack,…
mho22 Sep 3, 2026
7395ba0
feat(dri/omarchy): one Terminal entry, island.qml by default, and a l…
mho22 Sep 3, 2026
7e6b81a
fix(coreutils-docs): the man-page capture receives the kernel as a de…
mho22 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ import {
OMARCHY_FONTS_CONF,
OMARCHY_MAKO_CONFIG,
OMARCHY_MAKO_CONFIG_PATH,
OMARCHY_QUICKSHELL_DIR,
OMARCHY_QUICKSHELL_SHELLS,
OMARCHY_THEME_DIR,
OMARCHY_THEME_HOOK,
OMARCHY_THEMES,
Expand Down Expand Up @@ -275,6 +277,18 @@ const OPTIONAL_BINARY_URLS = {
...import.meta.glob("../../../../../binaries/programs/wasm32/foot.wasm", {
query: "?url", import: "default",
}),
...import.meta.glob("../../../../../local-binaries/programs/wasm32/qtgallery.wasm", {
query: "?url", import: "default",
}),
...import.meta.glob("../../../../../binaries/programs/wasm32/qtgallery.wasm", {
query: "?url", import: "default",
}),
...import.meta.glob("../../../../../local-binaries/programs/wasm32/quickshell.wasm", {
query: "?url", import: "default",
}),
...import.meta.glob("../../../../../binaries/programs/wasm32/quickshell.wasm", {
query: "?url", import: "default",
}),
} as Record<string, () => Promise<string>>;

async function optionalBinaryUrl(
Expand Down Expand Up @@ -1920,11 +1934,16 @@ async function bootProfile(
const busEnv = omarchy
? [`DBUS_SESSION_BUS_ADDRESS=unix:path=${OMARCHY_BUS_SOCKET}`]
: [];
// QtQuick clients (quickshell) must select the software scenegraph
// — there is no GL surface — and `dispatch exec` spawns them with
// the compositor's environment.
const quickEnv = omarchy ? ["QT_QUICK_BACKEND=software"] : [];
const compositorExit = spawnBg("/usr/local/bin/wlcompositor", "wlcompositor", [
"WLC_LAYOUT=dwindle",
"WLC_CONFIG=/etc/kandelo/wlcompositor.conf",
`WLC_SCALE=${outputScale}`,
...busEnv,
...quickEnv,
]);

if (omarchy) {
Expand Down Expand Up @@ -2224,13 +2243,15 @@ async function stageWaylandDesktopRuntime(
if (!profile.omarchyDemo) return;

const [barBytes, launcherBytes, daemonBytes, makoBytes,
notifySendBytes, footBytes, fontBytes] = await Promise.all([
notifySendBytes, footBytes, qtgalleryBytes, quickshellBytes, fontBytes] = await Promise.all([
fetchBinary("waybar.wasm", "waybar.wasm"),
fetchBinary("wldesktop/klauncher.wasm", "klauncher.wasm"),
fetchBinary("dbus/dbus-daemon.wasm", "dbus-daemon.wasm"),
fetchBinary("mako/mako.wasm", "mako.wasm"),
fetchBinary("wldesktop/notify-send.wasm", "notify-send.wasm"),
fetchBinary("foot.wasm", "foot.wasm"),
fetchBinary("qtgallery.wasm", "qtgallery.wasm"),
fetchBinary("quickshell.wasm", "quickshell.wasm"),
fetch(inconsolataFontUrl).then(failOn("Inconsolata-Regular.ttf"))
.then((r) => r.arrayBuffer()),
]);
Expand All @@ -2248,6 +2269,11 @@ async function stageWaylandDesktopRuntime(
writeVfsBinary(fs, "/usr/local/bin/notify-send", new Uint8Array(notifySendBytes), 0o755);
writeVfsFile(fs, "/usr/local/bin/omarchy-theme-changed", OMARCHY_THEME_HOOK, 0o755);
writeVfsBinary(fs, "/usr/local/bin/foot", new Uint8Array(footBytes), 0o755);
writeVfsBinary(fs, "/usr/local/bin/qtgallery", new Uint8Array(qtgalleryBytes), 0o755);
writeVfsBinary(fs, "/usr/local/bin/quickshell", new Uint8Array(quickshellBytes), 0o755);
ensureDirRecursive(fs, OMARCHY_QUICKSHELL_DIR);
for (const [file, qml] of Object.entries(OMARCHY_QUICKSHELL_SHELLS))
writeVfsFile(fs, `${OMARCHY_QUICKSHELL_DIR}/${file}`, qml, 0o644);
ensureDirRecursive(fs, "/etc/dbus-1");
writeVfsFile(fs, "/etc/dbus-1/session.conf", OMARCHY_DBUS_SESSION_CONF, 0o644);
ensureDirRecursive(fs, dirname(OMARCHY_MAKO_CONFIG_PATH));
Expand Down
116 changes: 109 additions & 7 deletions apps/browser-demos/pages/kandelo/kernel-host/omarchy-desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const OMARCHY_WAYBAR_CONFIG_PATH = "/usr/share/kandelo/waybar/config.json
export const OMARCHY_WAYBAR_STYLE_SEED_PATH = "/usr/share/kandelo/waybar/style.css";
export const OMARCHY_WAYBAR_STYLE_PATH = "/tmp/waybar-style.css";
export const OMARCHY_MAKO_CONFIG_PATH = "/usr/share/kandelo/mako/config";
export const OMARCHY_QUICKSHELL_DIR = "/usr/share/kandelo/quickshell";

/**
* The compositor config. SUPER is what real Hyprland (and Omarchy) binds, but
Expand Down Expand Up @@ -109,22 +110,23 @@ bind = CTRL SHIFT, 3, movetoworkspace, 3
* and not a list inside the launcher.
*/
export const OMARCHY_APPS: Record<string, string> = {
"terminal.conf": "name = Terminal\nexec = /usr/local/bin/wlterm\n",
"clock.conf": "name = Clock\nexec = /usr/local/bin/wlclock\n",
"paint.conf": "name = Paint\nexec = /usr/local/bin/wlpaint\n",
"vim.conf": "name = Vim\nexec = /usr/local/bin/wlterm /usr/bin/vim\n",
"nethack.conf": "name = NetHack\nexec = /usr/local/bin/wlterm /usr/bin/nethack\n",
"nano.conf": "name = Nano\nexec = /usr/local/bin/wlterm /usr/bin/nano\n",
"bash.conf": "name = Bash\nexec = /usr/local/bin/wlterm /usr/bin/bash -i\n",
"foot.conf":
"name = Foot\nexec = /usr/local/bin/foot --term=vt100 --override=main.workers=0 /usr/bin/bash -i\n",
"name = Terminal\nexec = /usr/local/bin/foot --term=vt100 --override=main.workers=0 /usr/bin/bash -i\n",
"qtgallery.conf": "name = Theme Gallery\nexec = /usr/local/bin/qtgallery\n",
"quickshell.conf":
"name = Quickshell\nexec = /usr/local/bin/quickshell -p /usr/share/kandelo/quickshell/island.qml\n",
};

/**
* The fontconfig configuration foot reads at startup. The demo stages one
* font (Inconsolata) under /usr/share/fonts and aliases the generic
* "monospace" family to it, so foot's default font pattern resolves without
* a per-user configuration.
* The fontconfig configuration foot and qtgallery read at startup. The demo
* stages one font (Inconsolata) under /usr/share/fonts and aliases the
* generic "monospace" and "sans-serif" families to it, so both clients'
* default font patterns resolve without a per-user configuration.
*/
export const OMARCHY_FONTS_CONF = `<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
Expand All @@ -135,6 +137,10 @@ export const OMARCHY_FONTS_CONF = `<?xml version="1.0"?>
<family>monospace</family>
<prefer><family>Inconsolata</family></prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer><family>Inconsolata</family></prefer>
</alias>
</fontconfig>
`;

Expand Down Expand Up @@ -172,6 +178,102 @@ text-color=#c0caf5
border-color=#7aa2f7
`;

/**
* The staged Quickshell shells, one QML file each: the shell IS a QML file,
* so swapping the file swaps the shell — the demonstration Quickshell exists
* for, and what qtgallery's shell cards do. island.qml is the default the
* launcher entry runs. All three render through QtQuick's software scenegraph
* (QT_QUICK_BACKEND=software comes from the compositor's environment) and
* anchor to the bottom edge because Waybar owns the top. The palette is the
* tokyo-night the desktop boots with; Quickshell reads QML once at startup,
* so like mako it keeps its colours across theme switches.
*
* The tab's per-worker compiled-code budget bounds these shells (see
* docs/browser-support.md#quickshell-qml-limits): each guest thread holds
* its own compiled copy of the Qt module, and the QML content decides how
* much cold Qt/ICU code each worker compiles. Single-threading the Qt
* client roughly halved the baseline, but a shell that touches heavy code
* paths still crosses the ceiling. Each shell here stays in the tested
* envelope — one item, numeric date fields (no localized dddd/MMMM name
* fields), regular weight — which compiles little beyond the base client.
*/
export const OMARCHY_QUICKSHELL_SHELLS: Record<string, string> = {
"shell.qml": `import Quickshell
import QtQuick

ShellRoot {
SystemClock {
id: clock
precision: SystemClock.Seconds
}
PanelWindow {
anchors {
bottom: true
left: true
right: true
}
implicitHeight: 30
color: "#1a1b26"
Text {
anchors.centerIn: parent
color: "#c0caf5"
text: "Quickshell — " + Qt.formatDateTime(clock.date, "hh:mm:ss")
}
}
}
`,
"island.qml": `import Quickshell
import QtQuick

ShellRoot {
SystemClock {
id: clock
precision: SystemClock.Seconds
}
PanelWindow {
anchors {
bottom: true
}
margins {
bottom: 14
}
implicitWidth: 380
implicitHeight: 44
color: "#1a1b26"
Text {
anchors.centerIn: parent
color: "#7dcfff"
text: "[ island ] " + Qt.formatDateTime(clock.date, "hh:mm:ss")
}
}
}
`,
"status.qml": `import Quickshell
import QtQuick

ShellRoot {
SystemClock {
id: clock
precision: SystemClock.Seconds
}
PanelWindow {
anchors {
bottom: true
left: true
right: true
}
implicitHeight: 34
color: "#16161e"
Text {
anchors.centerIn: parent
color: "#c0caf5"
text: "KANDELO " + Qt.formatDateTime(clock.date, "yyyy-MM-dd hh:mm:ss")
}
}
}
`,
};

/**
* Waybar config, translated from Omarchy's (config.jsonc): same top bar
* with workspaces left and the clock centered. The module lineup keeps
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-demos/pages/kandelo/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const PRESET_LIBRARY: Preset[] = [
{
id: "omarchy",
title: "Omarchy desktop",
summary: "The Omarchy desktop on Kandelo: the tiling compositor with the shell Omarchy is made of. Unmodified Waybar — the real GTK3 status bar, on wlr-layer-shell, its hyprland modules reading the compositor's Hyprland IPC sockets — reserves the top strip (workspaces, focused window, clock) and the windows tile beneath it. CTRL+Space opens klauncher, a keyboard-exclusive overlay launcher reading /usr/share/kandelo/apps; type to filter and Enter to launch. CTRL+SHIFT+Space cycles six themes (Tokyo Night, Catppuccin, Gruvbox, Nord, Everforest, Rosé Pine), each with its own Omarchy background image, recolouring the borders, gaps, wallpaper, bar and launcher live from one palette file. CTRL+Return spawns a terminal, CTRL+K a clock, CTRL+P a paint canvas, CTRL+W closes, CTRL+1..9 switch workspaces — every bind mirrored on SUPER for real Hyprland.",
summary: "The Omarchy desktop on Kandelo: the tiling compositor with the shell Omarchy is made of. Unmodified Waybar — the real GTK3 status bar, on wlr-layer-shell, its hyprland modules reading the compositor's Hyprland IPC sockets — reserves the top strip (workspaces, focused window, clock) and the windows tile beneath it. CTRL+Space opens klauncher, a keyboard-exclusive overlay launcher reading /usr/share/kandelo/apps; type to filter and Enter to launch — including Theme Gallery, a QtGui application whose cards preview every installed theme and restyle the whole desktop on click, or swap the running Quickshell for another QML shell. CTRL+SHIFT+Space cycles six themes (Tokyo Night, Catppuccin, Gruvbox, Nord, Everforest, Rosé Pine), each with its own Omarchy background image, recolouring the borders, gaps, wallpaper, bar and launcher live from one palette file. CTRL+Return spawns a terminal, CTRL+K a clock, CTRL+P a paint canvas, CTRL+W closes, CTRL+1..9 switch workspaces — every bind mirrored on SUPER for real Hyprland.",
base: SHELL_BASE,
packages: ["bash@local", "coreutils@local"],
accent: "#7aa2f7",
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-demos/source-only-vite-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const MODULE_PREFIX = "virtual:kandelo-source-only-asset:";
const PUBLIC_PREFIX = "__kandelo_source_only_assets__";

/** Bound the complete Vite snapshot set before the consumer allocates bytes. */
export const SOURCE_ONLY_VITE_RETAINED_MAX_BYTES = 512 * 1024 * 1024;
export const SOURCE_ONLY_VITE_RETAINED_MAX_BYTES = 1024 * 1024 * 1024;

interface RetainedSourceOnlyAsset {
relPath: string;
Expand Down
96 changes: 77 additions & 19 deletions apps/browser-demos/test/kandelo-omarchy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const SETUP_FAILURE =
* CTRL+Space, and switchable themes — and every piece is driven from the
* keyboard. Skips (via gotoOrSkip) when the binaries aren't built.
*/
test("Kandelo omarchy boots a themed tiling desktop with a bar, a launcher, and live theme switching", async ({ page }) => {
test("Kandelo omarchy boots a themed tiling desktop with a bar, a launcher, and live theme switching", async ({ page, browserName }) => {
test.setTimeout(300_000);

await gotoOrSkip(page, "/?demo=omarchy");
Expand Down Expand Up @@ -200,8 +200,9 @@ test("Kandelo omarchy boots a themed tiling desktop with a bar, a launcher, and
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/KLAUNCHER_READY n=8/);

// "te" narrows the eight entries (Bash, Clock, Foot, Nano, NetHack, Paint,
// Terminal, Vim) to Terminal alone — "t" alone still matches Paint.
// "te" narrows the eight entries (Clock, Nano, NetHack, Paint, Quickshell,
// Terminal, Theme Gallery, Vim) to Terminal alone — "t" alone still
// matches Paint.
await openSurface(page, "Demo");
await page.locator("body").click({ position: { x: 5, y: 5 } });
await page.keyboard.press("KeyT");
Expand All @@ -212,23 +213,36 @@ test("Kandelo omarchy boots a themed tiling desktop with a bar, a launcher, and
.toMatch(/KLAUNCHER_FILTER q=te n=1/);

// Enter launches the one match (Terminal) through the compositor's kwlctl
// socket and dismisses the launcher. The desktop went in with three tiled
// windows, so the launched terminal shows up as a fourth tile — the
// connection count alone would not prove it, since the launcher's own
// session ends at the same moment and frees its slot.
// socket and dismisses the launcher. The entry runs an unmodified upstream
// client, stock foot 1.17.2 — wl_display_connect via XDG_RUNTIME_DIR,
// fontconfig resolving "monospace" through the staged fonts.conf, fcft
// rasterizing the staged Inconsolata. The desktop went in with three tiled
// windows, so foot shows up as a fourth tile — the connection count alone
// would not prove it, since the launcher's own session ends at the same
// moment and frees its slot.
await openSurface(page, "Demo");
await page.locator("body").click({ position: { x: 5, y: 5 } });
await page.keyboard.press("Enter");
await openSurface(page, "Internals");
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/KLAUNCHER_EXEC cmd=\/usr\/local\/bin\/wlterm/);
.toMatch(/KLAUNCHER_EXEC cmd=\/usr\/local\/bin\/foot /);
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/KLAUNCHER_EXIT/);
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.poll(() => syslogStream(page), { timeout: 120_000 })
.toMatch(/TILE n=4 i=3 /);
expect(await syslogText(page), "foot binary does not match the kernel ABI")
.not.toMatch(/ABI version mismatch/);
// GLDRAW is the compositor's proof that it drew this window's texture.
// Every marker above is protocol — map, focus, tile — and all of them
// fire for a window whose wl_shm pool the GPU cannot import (a memfd
// pool instead of a gbm prime fd). foot carries the gbm-pool patch that
// keeps its pools importable; this is the gate that notices if it stops.
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/GLDRAW app_id=foot/);

// Gate 5b: a real application through the same path. "vi" narrows to Vim;
// its entry runs unmodified vim inside a wlterm, fetched lazily from
Expand All @@ -249,25 +263,69 @@ test("Kandelo omarchy boots a themed tiling desktop with a bar, a launcher, and
expect(await syslogText(page), "vim binary does not match the kernel ABI")
.not.toMatch(/ABI version mismatch/);

// Gate 5c: an unmodified upstream client through the same path. "fo"
// narrows to Foot; its entry runs stock foot 1.17.2 — wl_display_connect
// via XDG_RUNTIME_DIR, fontconfig resolving "monospace" through the staged
// fonts.conf, fcft rasterizing the staged Inconsolata — and the sixth tile
// only appears once foot maps its first frame through all of it.
const beforeFoot = await launcherSessions(page);
// Gate 5d: a Qt application through the same path. "ga" narrows to Theme
// Gallery; its entry runs qtgallery — QtGui's wayland QPA plugin connecting
// via XDG_RUNTIME_DIR, xdg-shell configure, the raster backing store
// through wl_shm, fontconfig resolving "sans-serif" through the staged
// fonts.conf — and the sixth tile only appears once Qt maps its first
// frame. The gallery reads the same six themes the compositor scanned.
const beforeQt = await launcherSessions(page);
await pressCtrl(page, "Space");
await expect
.poll(() => launcherSessions(page), { timeout: 60_000 })
.toBeGreaterThan(beforeFoot);
await pressKeys(page, ["KeyF", "KeyO", "Enter"]);
.toBeGreaterThan(beforeQt);
await pressKeys(page, ["KeyG", "KeyA", "Enter"]);
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/KLAUNCHER_EXEC cmd=\/usr\/local\/bin\/foot /);
.toMatch(/KLAUNCHER_EXEC cmd=\/usr\/local\/bin\/qtgallery/);
await expect
.poll(() => syslogStream(page), { timeout: 120_000 })
.toMatch(/GALLERY_PLATFORM=wayland/);
await expect
.poll(() => syslogStream(page), { timeout: 120_000 })
.toMatch(/GALLERY_THEMES n=6/);
await expect
.poll(() => syslogStream(page), { timeout: 120_000 })
.toMatch(/TILE n=6 i=5 /);
expect(await syslogText(page), "foot binary does not match the kernel ABI")
expect(await syslogText(page), "qtgallery binary does not match the kernel ABI")
.not.toMatch(/ABI version mismatch/);
// The invisible-window gate. Qt's stock backing store allocates memfd
// pools; the GL renderer cannot import those, skips the surface, and
// every gate above still passes — that is exactly how the invisible
// first Qt window shipped. Qt now carries the same gbm-pool patch as
// foot and GTK, and GLDRAW only fires once the window's texture was drawn.
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/GLDRAW app_id=qtgallery/);
// The card→dispatch→theme-switch loop is proven authoritatively by the Node
// smoke (host/test/qtgallery-smoke.test.ts injects a real pointer click on a
// card and reads the compositor's THEME line back). This browser gate proves
// the browser-specific half: Qt maps and its texture reaches the GL renderer.

// Gate 5e: a QtQuick application through the same path. "qu" narrows to
// Quickshell; its entry runs quickshell with the staged island.qml. The QML
// engine loads, the scenegraph renders through the software adaptation
// (QT_QUICK_BACKEND=software from the compositor's environ), and the
// PanelWindow maps as a wlr-layer-shell surface under Quickshell's default
// namespace — layer surfaces never emit GLDRAW, so the LAYER line is the
// mapping proof. Firefox is excluded: the running desktop's wasm code plus
// Quickshell's main and pthread modules exceeds SpiderMonkey's fixed 2 GiB
// per-process executable-code arena, so Quickshell's first QThread::start
// fails — see docs/browser-support.md#firefox-executable-code-limit.
if (browserName !== "firefox") {
const beforeQs = await launcherSessions(page);
await pressCtrl(page, "Space");
await expect
.poll(() => launcherSessions(page), { timeout: 60_000 })
.toBeGreaterThan(beforeQs);
await pressKeys(page, ["KeyQ", "KeyU", "Enter"]);
await expect
.poll(() => syslogStream(page), { timeout: 60_000 })
.toMatch(/KLAUNCHER_EXEC cmd=\/usr\/local\/bin\/quickshell/);
await expect
.poll(() => syslogStream(page), { timeout: 120_000 })
.toMatch(/LAYER ns=quickshell /);
}

// Gate 6: CTRL+SHIFT+Space cycles the theme. One palette file repaints the
// whole desktop — the compositor's borders, gaps and wallpaper, and the
Expand Down
Loading