Omarchy on Kandelo — the desktop shell: layer-shell bar, launcher, themes - #1241
Open
mho22 wants to merge 40 commits into
Open
Omarchy on Kandelo — the desktop shell: layer-shell bar, launcher, themes#1241mho22 wants to merge 40 commits into
mho22 wants to merge 40 commits into
Conversation
Contributor
Phase B-1 matrix build status —
|
| Package | Arch | Status | Sha |
|---|---|---|---|
| alsa-lib | wasm32 | failed | — |
| atk | wasm32 | failed | — |
| atkmm | wasm32 | failed | — |
| basu | wasm32 | failed | — |
| cairo | wasm32 | failed | — |
| cairomm | wasm32 | failed | — |
| expat | wasm32 | failed | — |
| fcft | wasm32 | failed | — |
| fmt | wasm32 | failed | — |
| fontconfig | wasm32 | failed | — |
| freetype | wasm32 | failed | — |
| fribidi | wasm32 | failed | — |
| gdk-pixbuf | wasm32 | failed | — |
| glib | wasm32 | failed | — |
| glibmm | wasm32 | failed | — |
| gtk-layer-shell | wasm32 | failed | — |
| gtk3 | wasm32 | failed | — |
| gtkmm3 | wasm32 | failed | — |
| harfbuzz | wasm32 | failed | — |
| jsoncpp | wasm32 | failed | — |
| libcurl | wasm32 | failed | — |
| libcxx | wasm32 | failed | — |
| libcxx | wasm64 | failed | — |
| libdrm | wasm32 | failed | — |
| libepoxy | wasm32 | failed | — |
| libevdev | wasm32 | failed | — |
| libffi | wasm32 | failed | — |
| libinput-lite | wasm32 | failed | — |
| libinput | wasm32 | failed | — |
| libpng | wasm32 | failed | — |
| libsigcxx | wasm32 | failed | — |
| libudev | wasm32 | failed | — |
| libwayland | wasm32 | failed | — |
| libxkbcommon | wasm32 | failed | — |
| libxkbregistry | wasm32 | failed | — |
| libxml2 | wasm32 | failed | — |
| libxml2 | wasm64 | failed | — |
| mtdev | wasm32 | failed | — |
| openssl | wasm32 | failed | — |
| openssl | wasm64 | failed | — |
| pango | wasm32 | failed | — |
| pangomm | wasm32 | failed | — |
| pcre2 | wasm32 | failed | — |
| pixman | wasm32 | failed | — |
| sdl2 | wasm32 | failed | — |
| spdlog | wasm32 | failed | — |
| sqlite | wasm32 | failed | — |
| sqlite | wasm64 | failed | — |
| tllist | wasm32 | failed | — |
| utf8proc | wasm32 | failed | — |
| zlib | wasm32 | failed | — |
| zlib | wasm64 | failed | — |
| bc | wasm32 | failed | — |
| bzip2 | wasm32 | failed | — |
| coreutils | wasm32 | failed | — |
| curl | wasm32 | failed | — |
| dash | wasm32 | failed | — |
| dbus | wasm32 | failed | — |
| diffutils | wasm32 | failed | — |
| dinit | wasm32 | failed | — |
| fbdoom | wasm32 | failed | — |
| file | wasm32 | failed | — |
| findutils | wasm32 | failed | — |
| foot | wasm32 | failed | — |
| gawk | wasm32 | failed | — |
| git | wasm32 | failed | — |
| grep | wasm32 | failed | — |
| gzip | wasm32 | failed | — |
| kandelo-sdk | wasm32 | failed | — |
| kernel | wasm32 | failed | — |
| less | wasm32 | failed | — |
| lsof | wasm32 | failed | — |
| m4 | wasm32 | failed | — |
| make | wasm32 | failed | — |
| mako | wasm32 | failed | — |
| mariadb | wasm32 | failed | — |
| mariadb | wasm64 | failed | — |
| msmtpd | wasm32 | failed | — |
| nano | wasm32 | failed | — |
| ncurses | wasm32 | failed | — |
| netcat | wasm32 | failed | — |
| nginx | wasm32 | failed | — |
| php | wasm32 | failed | — |
| posix-utils-lite | wasm32 | failed | — |
| sed | wasm32 | failed | — |
| spidermonkey | wasm32 | failed | — |
| tar | wasm32 | failed | — |
| tcl | wasm32 | failed | — |
| unzip | wasm32 | failed | — |
| userspace | wasm32 | failed | — |
| vim | wasm32 | failed | — |
| waybar | wasm32 | failed | — |
| wget | wasm32 | failed | — |
| xz | wasm32 | failed | — |
| zip | wasm32 | failed | — |
| zstd | wasm32 | failed | — |
| bash | wasm32 | failed | — |
| mariadb-test | wasm32 | failed | — |
| mariadb-vfs | wasm32 | failed | — |
| mariadb-vfs | wasm64 | failed | — |
| nethack | wasm32 | failed | — |
| node | wasm32 | failed | — |
| spidermonkey-node | wasm32 | failed | — |
| vim-browser-bundle | wasm32 | failed | — |
| nethack-browser-bundle | wasm32 | failed | — |
| rootfs | wasm32 | failed | — |
| shell | wasm32 | failed | — |
| lamp | wasm32 | failed | — |
| node-vfs | wasm32 | failed | — |
| wordpress | wasm32 | failed | — |
Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.
…me engine The Tier 1 / O1 desktop shell from the Hyprland-class compositor plan (docs/plans/2026-07-14-build-hyprland-class-compositor-plan.md §3). Compositor: zwlr_layer_shell_v1 (anchors, margins, exclusive zones, keyboard-interactivity). layers_arrange() computes the window work area from the anchored strips, background→overlay, then retiles. A surface joins the layer list at role creation, not at map, so a client dying mid-handshake cannot strand a strip. Compositing and hit-testing put background/bottom under windows and top/overlay over them on the GPU and CPU paths; layer surfaces show on every workspace. Theme engine: theme_scan/theme_load/theme_apply/theme_switch over /usr/share/kandelo/themes/<name>/theme.conf. `theme =` config key, `kwlctl theme` query, `dispatch theme <name|next|prev>`, a `theme` keybind dispatcher, and a `theme>>name` broadcast. Gaps and the focus border color are theme variables; the wallpaper repaints and re-uploads through a retained staging bo on switch. Fixes: SIGPIPE from a control client that exits without reading its reply no longer kills the compositor; a failed exec reports KWLCTL_EXEC_FAILED on stdout. libkwl: kwl_layer_create() + struct kwl_layer_opts; the connect path is factored into kwl_open()/kwl_open_buffers() shared with kwl_window_create(). New clients: kbar (top bar, exclusive zone, workspace pills + focused window + clock off the kwlctl socket) and klauncher (overlay launcher, keyboard-exclusive, filters /usr/share/kandelo/apps, Enter dispatches exec). Tests: layer-shell and theme Node smokes; the resize smoke now waits for both windows' resize markers instead of racing the second one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The O1 integration gate: the tiling compositor boots with kbar on a layer-shell strip, klauncher on SUPER/CTRL+Space, and three themes (Tokyo Night, Catppuccin Mocha, Gruvbox) cycling on CTRL+SHIFT+Space. omarchy-desktop.ts carries the data half: the compositor config, the launcher app registry, and the theme palettes. Staging and spawn reuse the hyprland block in live-setup.ts; only the extra files and the two extra binaries differ. Every SUPER bind is mirrored on CTRL because the browser reserves SUPER for the OS. Both hosts: the compositor, bar, launcher, and theme engine are gated by Node smokes (previous commit); the browser path is gated by the new kandelo-omarchy.spec.ts Playwright suite (config + theme loaded, bar anchored, windows tile under the bar, canvas composited, launcher takes the keyboard + filters + launches, theme cycles, bar tracks the workspace). Docs: architecture.md gains the desktop-shell subsection; browser-support.md gains the demo-table row and the Omarchy demo section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A theme's theme.conf gains `wallpaper = <file>`: KWLP raw pixels
("KWLP", u32le width/height, XRGB) that the compositor bilinear-scales
into g.wallpaper, falling back to the gradient when the key is absent
or the file fails to load. Raw pixels because nothing in the
compositor decodes PNG/JPEG — whoever stages the theme renders the
image.
The demo now ships six themes — Tokyo Night, Catppuccin Mocha,
Gruvbox, Nord, Everforest, Rosé Pine — each with an aurora wallpaper
the page renders at staging time (renderWallpaperKwlp: radial glows in
the palette colours on an OffscreenCanvas, 960x540, ~2 MB staged per
theme).
Fix exposed by per-theme gaps: wlterm only redrew + committed on a
KWL_RESIZE that changed its cell grid, but kwl_apply_resize has
already destroyed the buffer the compositor held — a tile shift
smaller than one cell (a theme's gap change) left the window invisible
until the shell printed again. wlterm now always recommits and gates
only the PTY winsize/SIGWINCH on a grid change. The other libkwl
clients already redrew unconditionally.
Both hosts: theme smoke gains the wallpaper triple (image, gradient
fallback, broken file) and the small-gap recommit regression (three
1px gap steps guarantee a grid-preserving resize); the Playwright gate
asserts WALLPAPER image and the widened theme-cycle set; hyprland +
omarchy browser gates green, all 10 wlcompositor/wlterm Node suites
green serially.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The launcher registry now offers the shell image's real software next to the demo clients: Vim, NetHack and Nano run unmodified inside a wlterm (`exec = /usr/local/bin/wlterm /usr/bin/vim`), plus a Bash terminal. The binaries lazy-fetch from the image's archives on first launch — the normal runtime path, no demo staging. wlterm now sets TERM=vt100 in its forkpty child: the inherited TERM describes the launcher's terminal, and curses apps must see the type this terminal implements. `klauncher --menu` opens the Omarchy menu: a root level (Apps, Theme) that descends into the app list or the installed-theme list (read from `kwlctl theme`, switched with `dispatch theme`). ESC in a submenu goes back to the root; ESC at the root dismisses. Bound to SUPER/CTRL+ALT+Space in the demo config. Compositor: the keybind engine gains the ALT (Mod1) modifier, and the self-contained xkb keymap gains Alt_L and the four arrow keys — the keymap had no arrows, so Up/Down never reached the launcher (or vim) in the browser. Both hosts: keybind smoke gains a CTRL ALT exact-mods case; theme smoke gains the menu flow (root → themes → dispatch, ESC back to root); the Playwright gate gains the vim launch through the launcher (lazy fetch + fifth tile + an ABI-mismatch guard) and the menu flow. All 10 wlcompositor/wlterm Node suites and the omarchy/hyprland/ wayland browser gates pass. The vim launch initially died on exec: the cached vim archive was built against ABI 15 (stale June link output reused by an incremental build). Rebuilt locally against the current sysroot; the published archive needs a re-publish for abi-v17. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
knotify — the notify-send slot: one toast per process. A corner- anchored overlay layer surface (top-right, under the bar's strip) shows `knotify <title> <body…>` for a moment and exits; the surface teardown is the dismissal, so there is no daemon and no unmap protocol. It adopts the compositor's live theme over kwlctl. The compositor gains a `notify = <path>` config key: a theme switch spawns the configured notifier with the new name — the hook Omarchy fills with notify-send in its scripts. Absent key, no spawn. libkwl: kwl_layer_opts gains margins (zwlr_layer_surface_v1.set_margin), which corner toasts need. kbar: the right side gains the kernel's monotonic uptime next to the clock, in the theme's muted colour. The kernel exposes no /proc/meminfo, so there is no memory module — real stats or none. Both hosts: theme smoke asserts the notify hook fires on a switch (Node); the Playwright gate asserts the toast maps and dismisses after the theme cycle. All 10 wlcompositor/wlterm Node suites and the omarchy/hyprland/wayland browser gates pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The browser input bridge translated keys purely by physical position (KeyboardEvent.code), and the kernel-side xkb keymap is a fixed US layout — so a non-US physical layout delivered the letter printed at the US position of the pressed key. On AZERTY the key labeled W sits at the QWERTY-Z position: Ctrl+W arrived at the compositor as Ctrl+Z and the killactive bind never fired, and every typed letter in wlterm or the launcher came out QWERTY-scrambled. charToKey maps a single typed ASCII letter to its US-layout keycode and wins over the positional path; everything else (digits, punctuation, dead keys, modifier-altered characters) keeps KeyboardEvent.code, because their key value depends on Shift and the layout's unshifted symbols. The Framebuffer pane's own translation stays positional on purpose — game movement keys are positional. Browser-only layer: Node has no keyboard capture (tests inject evdev codes directly), so there is no Node-side counterpart to change. Unit tests cover the AZERTY Ctrl+W pair and the non-letter fallback; verified live against /?demo=omarchy with synthesized code=KeyZ/key=w events (window closes), and the omarchy/hyprland/wayland/sdl2 browser gates pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tier 2 groundwork for unmodified foot (plan §4 PR19, server side). wlcompositor now advertises wp_presentation v1: clock_id is CLOCK_MONOTONIC — the clock kernel_vblank stamps page-flip events with — and each flip resolves the pending feedbacks with presented (flip timestamp, refresh interval from the mode's vrefresh, flip sequence, VSYNC flag) for visible surfaces and discarded for hidden ones. The SetCrtc first frame has no flip event, so it stamps itself with clock_gettime. Feedback resources queued on a dying surface are discarded before the free so their destructors don't dangle. The embedded xkb keymap gains F1-F12 and the nav cluster (Home/End/PgUp/PgDn/Insert/Delete) — foot and full-screen terminal apps send them; the map previously stopped at arrows. wlclient-test binds the global at registry time (clock_id arrives on the bind roundtrip), requests feedback on its mapping commit, and now compiles the received keymap with libxkbcommon and probes F1 + Delete by keycode. The smoke gate asserts PRESENTATION_CLOCK id=1, a PRESENTED line, no discard, and KEYMAP_SYMS f1=1 delete=1. All 14 wayland Node suites green serially. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First rungs of the Tier 2 font stack (plan §4 PR19): fcft composites glyphs through pixman and segments graphemes through utf8proc; freetype/fontconfig land next on top of them. pixman is the last autotools release (0.43+ is meson-only, and the flake carries no meson) cross-built like libpng with every SIMD backend disabled — wasm32 has none, the generic C paths are complete. Library-only build: the test/ and demo/ trees want a runnable host. utf8proc is one freestanding TU compiled directly, no upstream Makefile involved. build-programs.sh resolves both and symlinks archive + headers into the sysroot for the smoke drivers (pixman's headers flat because pixman.h angle-includes pixman-version.h and build_program adds no -I). pixman_smoke asserts an a8r8g8b8 fill and a solid OP_OVER composite pixel-exactly; utf8proc_smoke asserts NFC composition, tolower, and grapheme breaks. Both green under the kernel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… stack Completes the PR19 library ladder (plan §4): fontconfig resolves a family, freetype opens the TTF, fcft rasterizes into pixman — the path foot takes for every cell. freetype 2.13.3 is a plain autotools cross-build on the zlib dep; the optional deps (png, harfbuzz, brotli, bzip2) stay off. fontconfig 2.15.0 builds against libxml2 instead of expat (already ported) and needs three kinds of host-probe correction: glibc/BSD-only functions (random_r/initstate_r, getprogname, getexecname) forced off, and fstatfs off because fcstat's f_type branch is gated on __linux__, which this toolchain does not define. Its fcobjshash.h is generated at build time — the tarball ships only the .gperf input, so gperf joins flake.nix and [[host_tools]]. Runtime paths bake to /usr/share/fonts, /etc/fonts, and /tmp/fontconfig (the only world-writable dir). tllist 1.1.0 is a header-only stage. fcft 3.1.9 is meson-only upstream, so it gets the libxkbcommon-style bypass: the three upstream header generators run (version, unicode precompose, emoji data), then two TUs compile directly — no harfbuzz (shaping off, and with it utf8proc's only fcft hooks) and no SVG backend. fontstack_smoke stages fonts.conf + the in-tree Inconsolata TTF via FONTCONFIG_FILE, resolves "monospace" through an alias, and asserts a rasterized 'A' has nonzero ink. Green under the kernel alongside the pixman/utf8proc smokes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
foot's SIGCHLD reaper blocks the signal everywhere except inside epoll_pwait's atomic sigmask window — and never saw it: the wait hung until its timeout with the handler unrun, so a dead shell was never reaped (isolated by a C probe running exactly that pattern). Two composed gaps. sys_epoll_pwait swapped the mask around each attempt and restored it before parking on EAGAIN, so while parked the process's mask claimed the signal was blocked and sendSignalToProcess skipped the wake; it now uses the same sigsuspend-saved-mask pattern as sys_ppoll/sys_pselect6 (swap stays active across retries, kernel_dequeue_signal restores after delivering the signal that ended the wait). And the worker runtime converts epoll_pwait into timeout=0 poll attempts, which skip sys_poll's pending-signal check entirely — handleEpollPwait now swaps the mask through two new additive kernel exports (kernel_swap_poll_sigmask / kernel_restore_poll_sigmask, snapshot regenerated, no ABI bump) and ends the wait with EINTR when the per-attempt dequeue delivered a signal and no fd events were ready. Covered by a kernel unit test (pending-but-masked SIGCHLD + unblocking sigmask → EAGAIN park keeps the swap, retry EINTRs); 1114 cargo tests and the full host vitest suite green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three small holes real Wayland apps walk into. The overlay's input-event-codes.h stopped at BTN_EXTRA — foot's mouse-button mapping uses BTN_FORWARD/BACK/TASK. The wasm bits/fenv.h defined no exception flag names, so foot's box-drawing guard (fetestexcept(FE_INVALID|...)) failed to compile; they exist now as zeros — no flag can ever be raised on wasm's non-trapping FP, and fetestexcept(0) is always 0. And the rootfs gains /usr/share/X11/xkb: upstream xkbcommon's xkb_context_new(NO_FLAGS) returns NULL outright when its default include root is missing, which every stock client hits before it can compile the compositor's keymap string. Empty is fine — nothing reads xkb data from disk. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
data-device stub, seat v5, wl_surface.enter foot refuses to start without wl_subcompositor and wl_data_device_manager, requires wl_seat v5, and sizes its fonts only after wl_surface.enter — none of which the compositor spoke. All four land, plus a real bug foot exposed. wl_subcompositor: thin parent-glued overlays. A subsurface never tiles, focuses, or hit-tests; it maps with its buffer, rides its parent's position (recomputed at composite time — tiling moves the parent under it), and draws right above the parent in both the CPU and GL paths. Destroy handles all three orders (subsurface first, wl_surface first, parent first). Mapped-subsurface pixel output has no direct gate yet — foot only maps them in its URL/search overlays, which the browser gate will drive. wl_data_device_manager v3: inert — accepts sources and selections, transfers nothing. Real clipboard data paths are the O2 tier's work. wl_seat v5: name event, keyboard repeat_info(25, 400), and a pointer frame event closing every enter/leave/motion/button burst. wl_surface.enter: bound wl_output resources are tracked per client and the matching enter is sent when a surface maps. The output also advertises scale(1) + done, and reports physical size 0x0 (unknown): it previously sent pixels as millimetres, and foot derived DPI 25.4 from the fake 86-inch panel and reloaded its fonts at a garbage size. Gated by foot-smoke plus the 14 wayland Node suites and the full host vitest run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Tier 2 PR19 gate (plan §4): stock foot runs under wlcompositor on the ported font stack, with two declared patches at the platform's compatibility boundaries. Meson-bypass build like fcft: the two upstream generators run (version.h, builtin terminfo via python — no tic), wayland-scanner generates client glue for the eleven protocols foot compiles against (eight XMLs newly vendored at the wayland-protocols 1.45 pin, and the package's inputs list now names every XML so edits invalidate the staged source), then 44 TUs compile directly. Fork instrumentation is mandatory — foot forks its shell. Patch 0001 (shm): a memfd MAP_SHARED mapping only writes back on msync/munmap under this kernel, so a memfd wl_shm pool never carries live pixels to the compositor. Pools become renderD128 dumb-bos passed by prime-fd — the same contract every in-tree client uses — one bo per pool, one buffer per pool, offset 0, SHM scrolling off. Patch 0002 (fonts): concurrent FcFontMatch calls race pattern refcounts under this kernel's thread model and return garbled sizes; the four font variants load serially. host/test/foot-smoke.test.ts is the gate: foot connects via stock wl_display_connect (XDG_RUNTIME_DIR=/tmp), resolves "monospace" through a staged fonts.conf + the in-tree Inconsolata, composites onto card0 through the gbm pools (COMPOSITE_SAMPLE), tiles under dwindle, takes host-injected keys spelling "exit", and both foot and the compositor exit 0. Green in 2.3s; clean-cache resolver build verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…auncher entry, run.sh wiring Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… static closure trampolines The Tier 3 PR20 gate (plan §4): real ffi_call (i64, floats, by-value structs) plus ffi_closure, replacing the Wayland-scoped shim. wasm32 cannot JIT trampolines, so both combinatorial halves are generated at build time by gen-dispatch.sh (bash + awk): the ffi_call signature dispatch (every arity <= 8 with at most two non-i32 args, all-i32 to the Wayland ceiling of 22, times five return classes — 4435 keys) and a static closure trampoline pool (810 classes / 3240 slots baked into the function table). ffi_core.c encodes the clang wasm32 lowering verified by probe: singleton structs travel as their scalar; every other by-value struct is a pointer to a caller-owned copy (the callee may clobber it, so ffi_call passes scratch copies); struct returns are a hidden leading sret pointer; i8/i16 travel caller-extended. Pointers classify by word size so the same core is correct on the native test leg. ffi_cif gained no fields: libwayland embeds it by value, so cached consumer archives stay ABI-compatible and the signature key is recomputed per call. Un-emitted signatures abort loudly printing their key. The dispatch is split into one leaf per (return class, arity), routed through a function-pointer table on the key's low byte. Fused into one function it is a ~275 KB sparse uint64 switch that kills the whole process with "Fatal process out of memory: Zone" when V8's optimizing tier compiles it (the foot smoke hit this); the clang driver runs wasm-opt -O2 post-link and binaryen re-inlines single-caller functions of any size, so only indirect-call routing holds the split. Core + dispatch and the closure pool are separate archive members: ffi_call-only consumers (libwayland) don't pull the pool. Gates: host/test/libffi-full-unit.test.ts — the 263-case matrix native and wasm-under-kernel (arities x types x call/closure, byval clobber isolation, ffi_arg widening, pool reuse) plus a function-size bound that fails if the dispatch ever fuses again; host/test/libffi-shim-unit.test.ts keeps the PR1 Wayland arity gate green through the rewrite. Full checklist green: cargo, vitest (two known stale-ABI failures), libc-test, Open POSIX, ABI snapshot (userspace-only — no bump). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yped callback thunks The Tier 2 PR21 gate (plan §4): glib as four static archives (libglib, libgmodule, libgobject, libgio) for wasm32, meson bypass against a hand-curated config.h + glibconfig.h (libxkbcommon pattern). gobject rides the PR20 libffi port for its generic closure marshaller. gio drops the gdbus object graph until PR22 lands a session dbus daemon: a giomodule patch guards the dbus/portal built-in type registrations behind GIO_DBUS_BUILTIN_MODULES, and the four portal entry points referenced behind glib_should_use_portal() become G_IO_ERROR_NOT_SUPPORTED stubs. gregex stays out (needs pcre2). The port's key discovery: wasm call_indirect checks exact signatures, and glib leans on arity-changing callback casts that every native ABI tolerates — each one traps at runtime with "null function or function signature mismatch". wasm-callback-signatures.patch routes them through typed thunks: free_full's GDestroyNotify-as-GFunc, compare funcs stored as GCompareDataFunc, the G_DEFINE_TYPE / G_DEFINE_INTERFACE / G_DEFINE_DYNAMIC_TYPE macros (patched to generate matching *_intern_* wrappers, so every macro user — all of gio, GTK later — is fixed at compile time), G_IMPLEMENT_INTERFACE via a new exported g_wasm_iface_init_thunk, pspec instance_init via type qdata, and gcancellable's connect-data trampoline. The pattern is documented in docs/porting-guide.md for the next GObject port. config.h turns off what the kernel cannot back or must not take: HAVE_POSIX_SPAWN (gspawn must fork/exec so fork instrumentation applies), HAVE_EVENTFD (GWakeup on pipe2), HAVE_FUTEX (GMutex on musl pthreads), inotify, xattr, splice. USE_SYSTEM_PRINTF and ENABLE_NLS are load-bearing: without them g_*printf and glibintl route to un-compiled fallbacks that -Wl,--allow-undefined silently converts into trapping null table entries. Gate: programs/glib_smoke_test.c + host/test/glib-smoke.test.ts — GMainLoop idle/timeout/unix-fd dispatch, GObject type + property + signal through the libffi generic marshaller, g_spawn_sync and g_spawn_async with child watch (the binary re-execs itself at /bin/glib_smoke_test). Full checklist green: cargo, vitest (two known stale-ABI failures), libc-test, Open POSIX, ABI snapshot (userspace-only — no bump). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…AL auth over SO_PEERCRED The Tier 2 PR22 gate (plan §4): a session dbus stack for wasm32 — expat 2.8.3 (the daemon's config-parser dependency), dbus 1.14.10 (dbus-daemon, dbus-send, dbus-monitor; the last autotools series — 1.16 is meson/cmake only), and glib rev2 adding the 27 upstream gdbus_sources TUs to gio. GApplication, the dbus-backed built-in modules, and the portal backends stay out; GIO_DBUS_BUILTIN_MODULES stays undefined until a port needs one. The port's key discovery: the SDK links with -Wl,--allow-undefined, so every AC_CHECK_FUNCS link test "succeeds", including for functions the sysroot lacks. Absent functions must be forced off with ac_cv_func_<name>=no, or guarded includes break the build (dbus's getpeerucred pulls Solaris ucred.h) or trap at runtime as null table entries. The hazard is documented in docs/porting-guide.md. Auth rides a new third glib patch, wasm-credentials.patch: a __wasm32__ branch in gio/gcredentialsprivate.h mirroring the Linux ucred block. The kernel backs SO_PEERCRED and sendmsg drops non-SCM_RIGHTS cmsgs, so GCredentials follows the Linux contract; without the branch GCredentials is unsupported, gdbus EXTERNAL auth claims uid -1, and dbus-daemon rejects the handshake. Gate: programs/glib_gdbus_smoke.c + host/test/dbus-smoke.test.ts — dbus-daemon --version, a dash-orchestrated session bus answering a dbus-send GetId round trip, and a two-process gdbus ping + notify-send-shaped Notify round trip with dbus-monitor eavesdropping the bus. Full checklist green: cargo, vitest (two known stale-ABI failures), libc-test, Open POSIX, ABI snapshot (userspace-only — no bump). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the kernel The Tier 2 PR23 render stack (plan §4), four ports on the PR19 font stack and the PR21 glib: harfbuzz 10.1.0 (upstream is meson-only, but the dist tarball ships a single-TU amalgam built for embedding — one wasm32posix-c++ invocation with the freetype and glib backends, first C++ library port, links libc++), fribidi 1.0.16 (plain autoconf), cairo 1.16.0 (last autotools release; image surfaces, ft/fc fonts, png — no GL, no X, no vector backends), and pango 1.42.4 (last autotools release; pango/pangoft2/pangocairo, deps probed through PKG_CONFIG_PATH over the resolved prefixes). glib grows two consumer-facing shims pango's configure expects: a gthread-2.0.pc (threading lives in libglib since 2.32; upstream still ships the .pc) and glib-mkenums installed into bin/ with a glib_mkenums pc variable (pango generates its enum types with it). cairo's png probe ignores png_CFLAGS/png_LIBS and asks pkg-config, so the build pins png_REQUIRES=libpng16 with PKG_CONFIG_PATH at the resolved libpng. Gate: programs/pango_cairo_smoke.c + host/test/pango-cairo-smoke.test.ts — fontconfig resolves "monospace" from a staged fonts.conf, pango itemizes and shapes "kandelo" through harfbuzz, pangocairo renders into an ARGB32 image surface, and the test asserts layout extents, inked pixels, and the exact FNV-1a pixel hash (byte-stable under the pinned stack; verified across two runs). Full checklist green: cargo, vitest (two known stale-ABI failures), libc-test, Open POSIX, ABI snapshot (userspace-only — no bump). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two link defects the GTK3 port surfaced. lld's default shadow stack is 64 KiB and sits directly above the data segment, so a frame larger than what is left of it writes into libc's BSS instead of trapping. gdk-pixbuf's load_from_stream keeps a 64 KiB buffer on the stack; the overrun showed up as mallocng ctx.active corruption far from the call. Every link line now asks for the 8 MiB Linux main-thread default: sdk/src/lib/flags.ts for SDK links, and the in-tree link flag lists that do not go through the SDK (scripts/build-programs.sh, the six test-suite runners, examples/dlopen, lsof). wasm32posix-cc re-ordered its link inputs, emitting sources, then objects, then archives, whatever order the caller wrote. Link-input order is semantic for wasm-ld: an object placed before an archive gets a signature-mismatch stub on an unprototyped call, while the reverse order binds the mismatched definition and emits invalid wasm. parseArgs now also records every bucketed argument in its original position (orderedArgs) and cc.ts emits that. Gate: sdk/test/cc.test.ts asserts the stack-size flag reaches the link line and that `foo.o -lz bar.o` keeps its order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The original commit made kernel_sendmsg gather and kernel_recvmsg scatter every iovec, and added the signalfd4/signalfd sigset arg descriptors. This base supersedes both: the canonical message wire flattens the iovec array before the kernel sees it (validate_canonical_message_iov_len admits only 0 or the flattened count), and host_abi.rs already carries the signalfd descriptors. The regression gate is kept: programs/sendmsg-iovec-gather.c + host/test/sendmsg-iovec-gather.test.ts drive the sd-bus empty-leading-iovec auth shape, a three-iovec gather, a two-iovec scatter, and an empty leading receive iovec that must not read as EOF. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A blocking syscall the kernel parks returns EAGAIN, not EINTR, and the host re-parks the process. A Handler signal dequeued on that path had its delivery info written into a channel nobody reads: the signal was consumed, its sa_mask stayed applied, and every later instance of it was blocked for good. Waybar sits in poll() and read(), so the theme switch's SIGUSR2 died in the park and the bar never reloaded. The host now leaves the signal queued while it re-parks, and instead ends the park when one is deliverable: it dequeues the signal and completes the syscall with EINTR. Whether the guest sees that EINTR follows the handler's flags. Only poll and select consult them in the kernel (SignalState::should_restart, which returns EINTR rather than parking); every other syscall parks without reading them, so the park itself carries no intent. The host reads sa_flags out of CH_SIG_FLAGS and sets the new channel::SIG_RESTART byte for an SA_RESTART handler; the glue re-issues the syscall after the handler returns and returns the EINTR otherwise. Restarting unconditionally hangs the standard shutdown loop, where the handler sets a flag and the EINTR is what lets the loop check it. channel_syscall.c splits into __do_syscall_once plus a loop, and __deliver_pending_signal reports whether to re-issue. SIG_RESTART sits at SIG_BASE + 44 inside the 48-byte delivery area the host already clears, so no existing offset moves. Gate: programs/signal-no-restart.c + host/test/signal-no-restart.test.ts — a handler with sa_flags = 0 interrupts a read() that nothing will ever satisfy. Replacing the flag read with a constant true makes the program hang. programs/signal-to- threaded.c covers the restart side, which is what signal() installs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> # Conflicts: # crates/shared/src/lib.rs # docs/architecture.md # host/src/generated/abi.ts # host/src/kernel-worker.ts # libc/glue/channel_syscall.c # tools/xtask/src/dump_abi.rs
…tted Two defects in the kernel worker's blocking-syscall bookkeeping, both found bringing the omarchy desktop up. A poll or ppoll that outlives its first pass is completed later from a host timer, without running the syscall again, and that completion went through completeChannel's argDescs path. pollfd[] is an inout buffer, so that path copies the kernel's scratch region back over the caller's array — and the scratch is shared by every process, holding whatever syscall ran last by the time the timer fires. dbus-daemon's 30-second auth timeout came back carrying another process's stat buffer; the daemon then polled a descriptor it had never opened and spun on "invalid request, socket fd 12 not open" with every bus client unserved. The new completePollTimeout writes the POSIX result of a timeout directly: return 0 and clear every revents. Three loops walked pendingPollRetries / pendingSelectRetries live. A poll that is still not ready re-registers inside retrySyscall, and a Map iterator visits an entry re-inserted after its key was deleted, so the walk never terminates — the kernel worker spins and every process on the machine stops. Waybar's signal thread sits in poll, so the SIGCHLD from wordexp's /bin/sh froze the whole desktop. All three now iterate a snapshot. Gate: programs/poll-timeout-preserves-pollfds.c — a child runs stat() in a loop to keep foreign data in the scratch while the parent's poll times out. programs/p_10_fork_with_polling_thread.c, wired into the fork-instrument coverage matrix as P-10, reproduces the SIGCHLD spin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clang lays out a callee's frame from a stack pointer it assumes is
16-byte aligned. musl's pthread_create aligns the new thread's stack to
sizeof(uintptr_t) — 4 here — and then subtracts struct start_args, so
the value it hands to clone is routinely 4 mod 8. On such a thread the
caller of a variadic function stores a 64-bit argument four bytes away
from where va_arg reads it.
Waybar exposed it: its GDBus client runs on a thread, and GIO builds the
EXTERNAL auth identity with g_strdup_printf("%lli", (gint64) uid). It
printed the high half, so every process claimed uid 0 and dbus-daemon
rejected the handshake. The worker now rounds the pointer down, which
stays inside the thread's own stack region.
Gate: programs/thread-vararg-alignment.c +
host/test/thread-vararg-alignment.test.ts — printf("%lld", 1000) from
the main thread and from a pthread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Tier 2 PR24 base (plan §4): five new ports over the PR23 render stack — pcre2 10.44, gdk-pixbuf 2.36.12, atk 2.36.0, libepoxy 1.5.4, and gtk3 3.24.34 with its wayland backend. glib rev3 grows what a toolkit needs: gregex.c against pcre2 (glibmm registers the GRegex error domain at init), gmodule-deprecated.c, the GApplication / GAction / GMenu / GNotification TUs in gio, and glib-genmarshal plus gmodule-no-export-2.0.pc and gio-unix-2.0.pc for consumers that probe those names. cairo rev3 turns on cairo-gobject and the pdf/ps/svg surfaces, which GTK3 hard-requires. cairo and pango each take a wasm-callback-arity patch, routing arity-changing casts (cairo_spline_add_point_func_t, GFunc) through typed wrappers — a native ABI tolerates the extra argument, wasm's call_indirect traps on it. libwayland now also builds and ships libwayland-egl.a, so a package build that links an executable no longer depends on scripts/build-programs.sh having run. The wasm32 sysroot has no dynamic loader, so gdk-pixbuf's loaders and GTK3's immodules are built in. gdk's wl_shm pools come from libgbm dumb BOs (wayland-shm-gbm-pool.patch) so the prime-fd import path the compositor already speaks carries them. glib-compile-resources and glib-compile-schemas are compiled C host tools, so they come from the nix host glib in flake.nix, alongside xorg util-macros for libepoxy's autoreconf. Gate: programs/gtk3_smoke.c + host/test/gtk3-smoke.test.ts — an unmodified GTK3 window with a label, through gdk-wayland, pango shaping and a cairo wl_shm render on wlcompositor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twelve ports finishing PR24's client side: fmt 11.2.0, jsoncpp 1.9.6, spdlog 1.15.3, libsigc++ 2.10.3, the gtkmm binding chain (glibmm 2.62.0, cairomm 1.12.2, pangomm 2.42.0, atkmm 2.28.0, gtkmm3 3.24.2), gtk-layer-shell 0.9.2, libxkbregistry 1.7.0, and Waybar 0.14.0 itself. Waybar is built unmodified. It links one added TU, src/glib-static-init.c: glib and gobject each initialise from a plain constructor, and upstream the dynamic loader guarantees glib's runs first. A fully static link runs them in link order, so gobject_init reaches g_quark_from_static_string before glib_init has allocated the quark table and g_quark_init's assertion aborts the process. A priority-101 constructor calling the idempotent glib_init sorts it. The musl overlay's linux/input-event-codes.h gains BTN_STYLUS, BTN_STYLUS2 and BTN_STYLUS3, which gdk's tablet code needs to compile. Gate: host/test/waybar-smoke.test.ts — the bar maps a layer-shell surface on wlcompositor and reloads its stylesheet on SIGUSR2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PR24 notification stack: basu 0.2.1 (sd-bus lifted out of systemd) and mako 1.10.0, upstream's Wayland notification daemon, owning org.freedesktop.Notifications on the dbus session bus and drawing on wlr-layer-shell. Three patches carry mako: its pool buffer allocates through a gbm dumb BO and hands the compositor the prime-fd (the same import path every other client uses); parse_boolean is renamed off a musl symbol clash; and its wl_registry listeners get typed no-op entries, since a missing entry is a null call_indirect on wasm. programs/notify-send.c is the sender the desktop calls — one org.freedesktop.Notifications.Notify over the session bus, printing the assigned id. glib_gdbus_smoke grows a --notify mode that does the same against whichever daemon owns the name. Gate: host/test/mako-smoke.test.ts — dbus-daemon, mako on the compositor, a Notify from the sender, and makoctl dismissing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… IPC What an unmodified GTK3 bar asks wlcompositor for. Three new globals. zxdg_output_manager_v1 v3 answers the logical geometry of the single fullscreen output — position (0,0), size = the mode, name "virtual-0" — as a one-shot burst, since it never changes. wp_viewporter carries a per-surface crop and scale: the committed size follows the protocol's destination-then-source-then-buffer rule, the CPU blit nearest-samples the source rect across the destination box, and the GL path passes the rect as a uv uniform. wp_fractional_scale_manager_v1 reports a fixed 120/120ths. wl_output moves to v4 so it can send name and description, which mako binds unconditionally. wl_shm pools accept a buffer at any offset — GTK packs several into one pool — so the import covers every row up to the buffer's end and the base pointer walks to its own first row. A buffer that starts partway into a pool has no GL texture of its own; the GL path now skips it instead of tearing down the EGL session, which is what libwayland-cursor's packed theme used to do to the whole desktop. The control socket gains a second face: $XDG_RUNTIME_DIR/hypr/ wlcompositor/.socket.sock and .socket2.sock, where Waybar's hyprland modules look. Requests answer in hyprctl -j shapes (clients, workspaces, activeworkspace, monitors), the j/ prefix is accepted and ignored, socket2 streams events from its first byte, and the event names carry their v2 variants. Titles reach the bar: xdg_toplevel set_title is recorded and JSON-escaped, since it is client-controlled text. A layer surface announces its LAYER marker when it maps, not when it is configured — a strip reserves nothing until then, and announcing early put it ahead of the windows re-tiling under it. A window that maps while the launcher holds the keyboard no longer steals it. Gate: host/test/wlcompositor-protocols-smoke.test.ts for the three globals; the kwlctl smoke covers both Hyprland sockets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The browser demo's shell is no longer ours. kbar and knotify are replaced by upstream Waybar on wlr-layer-shell and upstream mako on the dbus session bus, and the demo stages what they read: a Waybar config.jsonc and style.css translated from Omarchy's, a mako config, a session bus config with EXTERNAL auth, and the six themes' real Omarchy background images rendered to KWLP at staging time (the aurora spec stays as the fallback when a fetch or decode fails). The theme switch is Omarchy's shape. The compositor's `notify =` hook is a bash script that reads the new theme.conf, writes the bar's stylesheet from the same template the boot-time file comes from, signals every waybar it finds in /proc with SIGUSR2, then execs notify-send. Waybar reloads in place and mako draws the toast. dbus-daemon and mako start once both the display and bus sockets exist, and Waybar once the display, bus and Hyprland sockets do: each connects exactly once, with no retry. run.sh learns dbus, mako and waybar targets and adds them to the browser dependency set. Gate: apps/browser-demos/test/kandelo-omarchy.spec.ts — the bar as a layer surface with its IPC listener attached, windows tiling under the height it actually reserved, the desktop still on the GPU presenter after boot, a theme switch reaching the bar and the notification daemon, and a workspace switch arriving as Waybar's own IPC log line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The session handoffs written while the work in this branch was done, kept in docs/plans alongside the numbered series already there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kernel_sendmsg and kernel_recvmsg returned EINVAL when msg_iovlen exceeded IOV_MAX. Linux returns EMSGSIZE. Its __copy_msghdr path in net/socket.c serves both calls and rejects the array before it reads any iovec. The four readv/writev entry points keep EINVAL. POSIX specifies that errno for an iovcnt above IOV_MAX, so the two families differ on purpose. Each kernel site now carries that fact in a comment. IOV_MAX and UIO_MAXIOV are both 1024 in the sysroot, so the existing bound was already correct and only the errno changes. The change is semantic: it moves no snapshot offsets and needs no ABI_VERSION bump. Gate: programs/sendmsg-iovec-gather.c + host/test/sendmsg-iovec-gather.test.ts gain two cases. An array of exactly IOV_MAX sends and scatters its payload across the full array. An array one longer fails with EMSGSIZE on both calls. Each guard is pinned on its own: restoring EINVAL fails the test with "send over IOV_MAX errno=22", widening both guards to >= 1024 fails it with "send at IOV_MAX errno=90", and widening only the recvmsg guard fails it with "recv at IOV_MAX". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
buildKmsGlPresenter runs on the WebGL2 context the dead compositor was driving — getContext returns the canvas's existing context — and its reset list stopped at draw state. Pixel-store state survives too. A leftover UNPACK_ROW_LENGTH, UNPACK_SKIP_ROWS, UNPACK_SKIP_PIXELS or a still-bound PIXEL_UNPACK_BUFFER makes every scanout upload GL_INVALID_OPERATION. WebGL raises no JS exception for it, so presentKmsGlScanout reports success, slot 7 stays 2 and the commit count advances while the canvas is black. A leftover UNPACK_FLIP_Y_WEBGL or UNPACK_PREMULTIPLY_ALPHA_WEBGL uploads cleanly and corrupts the image instead: the scanout is XRGB8888 with X = 0, so premultiply zeroes the colour. All six are guest-reachable and none is restored on its own. OP_PIXEL_STOREI (host/src/webgl/bridge.ts) forwards any pname while host/src/webgl/shadow.ts shadows only unpackAlignment and packAlignment, and OP_BIND_BUFFER accepts any target. kernel-worker.ts is shared by both hosts, so the fix lands on Node and the browser at once. Node degrades to stats-only without WebGL2, which leaves the browser as the only real consumer of this path. Gate: host/test/dri-kms-stats-sab.test.ts. The fake GL records pixelStorei and bindBuffer, and the claim/release test asserts all six unpack defaults plus the buffer unbind on both rebuilds. Each reset is pinned on its own — deleting UNPACK_ROW_LENGTH fails the test with "pixelStorei(UNPACK_ROW_LENGTH, 0): expected [] to have a length of 2", and deleting the bindBuffer or the premultiply reset fails it the same way under their own labels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lease
host/test/dri-kms-stats-sab.test.ts pins the pixel-store resets against a
fake GL: it records the calls buildKmsGlPresenter makes. It cannot see
whether the uploads those calls protect are accepted, and a rejected
texImage2D raises no JS exception — which is the whole failure mode. Only
a real context answers it.
The fixture drives the shipped path, not a copy of it: it builds a
CentralizedKernelWorker in a worker, attaches a transferred OffscreenCanvas
with mode webgl2-scanout, stubs kms.currentFb / kms.scanoutBytes the way
the vitest suite does, and ticks the vblank pump. Between the two ticks it
sets every unpack pname and binds a PIXEL_UNPACK_BUFFER through
markKmsCanvasGlOwned, then releases — the EGL-teardown sequence.
The spec samples the composited page with page.screenshot({clip}). A
readPixels inside the worker cannot serve here: the drawing buffer and the
composited page disagree, which is the defect. locator.screenshot()
returns a stale rasterization.
The scanout is two-tone, and that is load-bearing. A rejected upload
leaves both halves black; a leftover UNPACK_FLIP_Y_WEBGL swaps them; a
leftover UNPACK_PREMULTIPLY_ALPHA_WEBGL zeroes the colour because the
scanout is XRGB8888 with X = 0. A uniform image would report the last two
as passing. Deleting the whole reset block fails the spec with "rebuilt
top half", and so does deleting the flip reset alone.
The fixture lives under apps/browser-demos/test/fixtures/ and is served by
the dev server only — vite.config.ts builds an explicit input list, so it
never enters the static build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The spec added in a845e83 was in the tree but in no workflow. The COI job is the only browser job that runs on a bare checkout, and the gate fits it: no binaries, no kernel wasm, ~2 s. Its own step rather than the existing three-browser line — the gate needs WebGL2 on an OffscreenCanvas transferred into a worker, which only the `chromium` channel provides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dows The demo opened a clock and two terminals for the user. It now opens nothing. Every client is one the user asks for, through the binds the compositor already loads from its own config (CTRL+K a clock, CTRL+Return a terminal) or through the launcher. The third terminal was also the keep-alive: `spawn` resolves on process exit, and awaiting it held the boot task open. The compositor is the process whose lifetime is the desktop's, so the omarchy branch awaits it instead. `spawnBg` returns its promise for that; the calls that stay fire-and-forget say so with `void`, as the rest of the file does. The hyprland demo keeps its three clients — its gates read the tiling itself. The spec opens the same three clients through the binds before the gates that need them, so gate 3 onward is unchanged. It also asserts the desktop is bare first, which the tile counts alone cannot: deleting that assertion and restoring the wlclock spawn fails it with "the desktop opened a clock on its own". Those assertions count `KBD_FOCUS app_id=` markers rather than tiles. Waybar leaves a second toplevel behind with an empty app_id once its bar is up, and it takes a tile slot — so a tile count answers "how many windows exist", not "which windows the demo opened". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Waybar's bar came up with a second window beside it: an empty app_id, no title, 10x16, that took the keyboard and a tile of its own. It is not a window at all. It is Waybar's cursor surface — GTK hands it to wl_pointer.set_cursor, which the compositor accepts and ignores because the host pointer already draws the sprite. surface_commit mapped it anyway. The map path ran for every surface that was neither a subsurface nor a layer surface, so a buffer alone was enough to become a window. The xdg_toplevel role is what makes a surface a window; a surface without one now commits its buffer and stops there. Every in-tree client already takes its role before its first commit — libkwl at kwl.c:609 and :663, wlclient-test, wldmabuf-test — so nothing that maps today stops mapping. The three gates in the omarchy spec that count tiles were each satisfied by the previous client: the phantom took slot 0, so "TILE n=4 i=3" after the launcher was already true at gate 3 time, and the same shift carried into vim and foot. They now prove what they say — making the launcher's kwlctl exec a no-op fails gate 5 on TILE n=4 i=3, where before it passed. The new assertion at gate 3 is what keeps the phantom from coming back: the three windows opened through the binds are the only tiles. Running the spec against the pre-fix binary fails it with "a surface with no window role took a tile". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Waybar-stack recipes declared libcxx@21, the version string the package carried on the old base. This base pins libcxx to the exact LLVM version from flake.nix (21.1.7) and the resolver refuses a projection whose dependency pins drift from the registry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wl_cursor_theme_load backs its theme pool with memfd_create + mmap(MAP_SHARED). The host's shared-mmap preflight rejected any regular file without a persistent host capability with ENOTSUP, so every GTK wayland client died in gdk's cursor-theme assert (_gdk_wayland_display_get_scaled_cursor_theme) before first draw. MAP_SHARED on non-regular files already falls back to fd-pread population without a writeback tracker entry. A kernel-backed regular file (memfd, synthetic /etc) now takes the same fallback: the pages fill at map time and writes stay local. Nothing reads a cursor pool back — the compositor accepts and ignores wl_pointer.set_cursor. Live coherence still needs a kernel-owned mapping bridge. The projection regen rides along (host/src is a build input of the package images). Gate: host/test/gtk3-smoke.test.ts (was aborting before draw) and the updated contract case in host/test/file-shared-memory.test.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test forced main-thread mode with a raw NodePlatformIO, where the kernel's authoritative spawn-target stat reaches the real host filesystem and /bin/glib_smoke_test cannot exist. Worker mode bakes execPrograms into the test rootfs, the same staging the dbus and dash-orchestrated smokes already use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bundle bakes in the generated ABI surface. The epoll mask-swap
commit added two kernel exports without regenerating it, so the
bundled resolver rejected every freshly built kernel.wasm ("Binary
exists but was rejected by artifact policy") and the libc/posix suite
runners died at kernel resolution. scripts/test-resolve-binary-bundle.sh
is the CI gate for this staleness.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mho22
force-pushed
the
explore-dri-hyprland
branch
from
August 21, 2026 10:03
3104fbc to
66c47da
Compare
mho22
force-pushed
the
explore-dri-omarchy
branch
from
August 21, 2026 10:03
29d653f to
272be07
Compare
mho22
marked this pull request as ready for review
August 21, 2026 14:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #976 (the tiling WM). This starts at the O1 milestone of
docs/plans/2026-07-14-build-hyprland-class-compositor-plan.md§3 (PR15–PR18) and continues past it: the desktop shell that turns the tiling compositor into an Omarchy-shaped desktop, then the identity work that makes it look and feel like Omarchy rather than a compositor test.Commits
zwlr_layer_shell_v1(anchors, margins, exclusive zones, keyboard-interactivity) with the arrangement engine that reserves strips and retiles beneath them; the file-based theme system (/usr/share/kandelo/themes/<name>/theme.conf,kwlctl dispatch theme,theme>>broadcast);kbarandklauncher;kwl_layer_create()in libkwl; a SIGPIPE fix (a control client exiting without reading its reply killed the desktop)./?demo=omarchy— the O1 integration gate: bar + launcher + themes, every SUPER bind mirrored on CTRL (browsers reserve SUPER).wallpaper = <file>(KWLP raw pixels, bilinear-scaled by the compositor; raw because nothing in C decodes PNG/JPEG — the page renders per-theme aurora wallpapers at staging time). Tokyo Night, Catppuccin Mocha, Gruvbox, Nord, Everforest, Rosé Pine. Fixes a wlterm bug this exposed: a resize smaller than one terminal cell left the window buffer-less and invisible.klauncher --menuis the Omarchy menu (Apps/Theme, ESC back to root) on SUPER/CTRL+ALT+Space; the compositor gains the ALT modifier and the keymap gains Alt_L + arrow keys (arrows never reached clients before); wlterm advertises TERM=vt100 to its child.notify = <path>spawns a themed overlay toast on every theme switch; kbar shows the kernel's monotonic uptime (no /proc/meminfo → no memory module; real stats or none).Both hosts
wlcompositor-{layer-shell,theme,keybind,resize,…}smokes — all 10 wlcompositor/wlterm suites pass serially, including new coverage for the wallpaper triple (image / gradient fallback / broken file), the grid-preserving-resize recommit regression, CTRL-ALT exact-mods binds, the menu flow, and the notify hook.kandelo-omarchy.spec.tsgrew from 7 to 10 gates (vim launch through the launcher incl. an ABI-mismatch guard, the menu flow, the toast lifecycle); omarchy + hyprland + wayland gates pass.Verification: cargo
-p kandelo --lib0 fail;run-libc-tests.sh/run-posix-tests.sh0 FAIL;check-abi-version.shin sync — everything here is additive userspace, noABI_VERSIONbump.Known issues carried in the tree (not this PR's regressions): the published vim archive is still abi-v15 and needs a matrix re-publish (a locally rebuilt vim.zip is what the demo uses meanwhile); the exclusive-zone release path (bar exits → tiling area grows back) remains untested — a prototype
dispatch closelayerexposed a host-layer deadlock when a layer client exits in reaction to a compositor event, which needs its own investigation.Docs: architecture.md (desktop shell, themes, menu, knotify), browser-support.md (Omarchy demo section), presets.