| Age | Commit message (Collapse) | Author |
|
Closes out Phase 0 with the measurement that explains the ~30fps: a per-frame
budget breakdown and a non-blocking GPU timer query.
- GlBridge: load EXT_disjoint_timer_query (glGenQueriesEXT/Begin/End/
GetQueryObject*), gated by the GL extension string; report gpu_timer in the
bring-up line. Absent -> graceful 'n/a'.
- PresentTarget::render(ctx, RenderTimings*): time clear/update/render-submit/
present (CPU wall-clock) and bracket ctx->Render() with a 2-deep timer-query
ring read back a frame late, so the REAL GPU fill time is measured without a
glFinish stall. delete_queries in teardown.
- composite_frame: time the import+layout loop; accumulate all phases; emit a
[perf-split] line (per-rendered-frame averages) alongside [perf].
Result (headless 1080p, Haswell+crocus == CF-AX3 GPU class): CPU work ~2ms
(import 0.7 / update 0.7 / render-submit 0.8), GPU fill (ctx->Render) ~10-15ms,
present dominated by the blocking fence wait. -> fill-bound; the whole-output
composite is the wall. Damage limiting is the lever, to be built properly in
the Phase 1 compositor (not hacked into this throwaway). --verify ALL PASS,
kernel suite green.
|
|
The bug: route_point mapped the cursor with transform-UNAWARE math —
`(screen - GetAbsoluteOffset) / box`. screen coords are in transformed screen
space; GetAbsoluteOffset is in untransformed layout space. They coincide only
for an axis-aligned window, so plain --run tested fine, but on a --demo corner
(perspective + rotateX/rotateY) they diverge and clicks landed in the wrong
spot inside the client. --verify criterion 3 stayed green because it exercised
a DIFFERENT, correct path (unproject_to_local) the live code never called.
The fix: project the screen point onto the element's own plane FIRST via
RmlUi's transform-aware Element::Project() (ray/plane intersect through the
element's accumulated transform), then do the box->surface-local math in the
matching space. Project() is a no-op when untransformed, so --run is unchanged.
This is the live-path analogue of the pure-core inverse criterion 3 verifies.
Debug tool (press D in --demo): a per-surface magenta crosshair drawn as a
child of the hovered window, so RmlUi renders it THROUGH that window's own 3D
transform — it sits exactly under the wlr cursor iff the mapping is correct, so
any gap is the live click error, visible live (no screenshots). A #dbg readout
prints screen -> elem-local -> surface-local (also to the diagnostic log). 'D'
is consumed so it never types into the focused client.
Confirmed accurate on the real CF-AX3 seat. --verify ALL PASS, kernel suite
green, spike builds clean.
|
|
New `--demo` mode for a real-seat perf load test (the Phase-0 perf measurement):
- Four windows, one per screen corner, angled INWARD (perspective + per-corner
rotateX/rotateY) — a curated layout, distinct from --run's single centered
window.
- Clients: 3x foot + 1x vivaldi (Chromium on Wayland via
--ozone-platform=wayland; probes vivaldi-stable/vivaldi/vivaldi-snapshot,
loud warning if absent). Replaces firefox, which rendered as an empty/clear
box on this compositor. Each client claimed into the next free corner slot at
map time (browser steered to its designated corner via app_id match;
vivaldi/chromium/firefox all recognized); per-slot bookkeeping freed on
unmap/destroy.
- Live FPS HUD (RmlUi text) in the corner; per-5s min/max FPS written to
$UNBOX_SPIKE_FPS_LOG (default $HOME/rml-spike-fps.log), fflush+fsync per line.
- Demo dead-man defaults to 120s (P resets; Esc/Ctrl+Alt+Backspace quit) so an
HD video can play while watching FPS.
All existing behavior intact (live-update loop, input routing through the
transform, cursor, escape hatch, persistent log). --verify ALL PASS, kernel
suite green, spike builds clean (build_by_default:false, out of the shipped
binary). Headless smoke: 3 foot + vivaldi map into 4 distinct corners; 5s
min/max FPS lines written.
|
|
Iterated the Phase-0 spike from "self-verified headless" to "works on the
CF-AX3": a live, 3D-tilted client window you can type into, with a visible
cursor and pointer/touch routed through the transform. Real-seat confirmed.
Safety / escape (a black-screen lockout had forced reboots):
- session escape hatch on --run (mirrors src/input.cpp): Esc + Ctrl+Alt+Backspace
quit, Ctrl+Alt+F1..F12 VT-switch, SIGINT/SIGTERM clean-terminate.
- 15s DEAD-MAN auto-exit, reset by pressing P (also a keyboard-liveness probe);
UNBOX_SPIKE_TIMEOUT overrides. Guarantees the spike can never lock the machine.
- persistent crash-survivable log at $HOME/rml-spike.log (UNBOX_SPIKE_LOG),
fflush+fsync per line (was on tmpfs /tmp -> lost on reboot); loud diagnostics
for backend/modeset/client-connect/map/import.
Black screen / no window fixes:
- present path works on real DRM+gles2 (blue bg + marker confirmed on panel);
dark-blue clear + marker rect make "empty vs not-presenting" diagnosable.
- xdg wired from new_toplevel/new_popup (was new_surface -> no role -> no
configure -> client never mapped); foot now maps as a live surface element.
- WAYLAND_DISPLAY exported so spawned clients connect.
Interactivity:
- keyboard focus enter + key forward to the focused client (was never sent
without a pre-existing keyboard device); typing reaches foot.
- visible wlr-plane cursor (xcursor theme load + set; never drawn into RmlUi).
- pointer + touch hit-tested via RmlUi pick through the 3D transform, mapped to
surface-local, forwarded via wl_seat (criterion-3 pointer confirmed at edges).
Live update loop (was frozen on frame #1):
- send wlr_surface_send_frame_done to all mapped surfaces+subsurfaces each frame
(client lives as an imported texture, not a wlr_scene node).
- re-import the surface's CURRENT buffer per commit, gated on the surface commit
SEQUENCE not the buffer pointer (foot recycles a buffer pool -> same pointer,
new contents); double-buffered wlr_buffer lock/release so the client is never
starved. reimports now track commits ~1:1 (was capped 3:1).
--verify ALL PASS (criteria 1-7), kernel suite green, build + build-asan clean.
Spike target stays build_by_default:false, out of the shipped binary.
|
|
Throwaway, self-contained spike target `packages/kernel/rml-compositing-spike`
(build_by_default:false, not in kernel_dep) proving RMLUi can composite live
client windows. `--verify` reads back the framebuffer headlessly and asserts all
7 criteria; `--run` brings up a real/nested-seat compositor for the user's
real-seat run. All 7 = ALL PASS on this dev box (an Intel Haswell-ULT iGPU on
Mesa crocus — the CF-AX3's GPU class):
1 zero-copy live dmabuf→EGLImage→GL texture sampled by RmlUi, cached (0 reimport
when buffer unchanged); 2 RCSS perspective+rotateY on the live pixels (verified
by readback); 3 screen→surface-local inversion through the 3D transform,
round-trip 0.000000 px (pure core doctested in the kernel suite); 4 surface
tree (toplevel+subsurface+popup) composited correctly → recommend
PER-SUBSURFACE elements (RTT escape-hatch for tree-spanning effects in Phase 1);
5 wallpaper (layer surface) via the identical import path; 6 idle dirty-gate =
0 renders over 120 idle turns, exactly 1 render per commit; 7 present path
FBO→dmabuf swapchain→wlr_scene_buffer with an EGL fence (no glFinish).
Crocus gotchas documented (linear modifiers, AR24 swizzle, cross-context fence,
FBO Y-flip, re-import only on new buffer). Real-seat GO/NO-GO (3D/touch feel,
frame-time @4 windows+video, idle power) is the user's call — runbook in
reports/rml-compositing-spike.md §5.
Also: GLOSSARY rows for "RML compositing" + "surface element" (user-confirmed);
tasks.md slice 13 updated to spike-complete/GO pending real-seat.
kernel suite green; full build clean; spike --verify ALL PASS.
|
|
Two additive primitives for C++-driven, RCSS-tunable animation:
- Host::request_frames(cb) -> FrameRequest: a per-frame callback (RAII handle)
run before tick_all each frame; the kernel schedules frames continuously while
>=1 request is alive and stops at rest. Fills the missing animation timer.
- UiSurface::transition_timing(element_id, property): reads the RCSS-authored
transition duration + easing, returning RmlUi's tween wrapped as a pure
std::function (no RmlUi types cross the contract) so an extension can drive its
own animation with hot-reloadable, designer-tunable timing/easing.
|
|
The shader-based gradient (linear/radial) fragment shader now adds a
mean-zero triangular-PDF dither (~1 LSB of 8-bit) from a spatial pixel-position
hash before writing finalColor, so smooth ramps (e.g. an alpha fade) no longer
show quantization banding when written to the 8-bit surface buffer. Spatial,
non-temporal — no shimmer on static panels. Applies to every shader gradient.
|
|
Forwards RmlUi Dragstart/Drag/Dragend for a named callback as DragPhase
{start,move,end} with surface-local x/y, so an extension can drive an
interactive drag from a captured ui-surface touch (the touch bus never
sees it). Mirrors bind_event's error-isolation + hot-reload handling.
|
|
.rml basename)
The watch_file refactor (35e5d32) moved the substrate's UI-asset hot-reload onto
the shared FileWatcher but armed a BASENAME watch on the document's .rml file only.
The dock's styling lives in a separately-<link>ed dock.rcss, so editing it (the
common case) never matched the watch — asset hot-reload silently stopped working on
the real seat (no "dev hot-reload ON" line, no reload on save), while config
watching kept working. The ui_reload_surface() seam test passed because it bypassed
the real inotify->reload path.
Fix: FileWatcher::add_dir watches the document's whole DIRECTORY (so any .rml/.rcss
in it triggers the surface reload); the substrate uses it and restores the
"dev hot-reload ON (inotify watching asset dir '...')" log. Added an END-TO-END
test mirroring the dock (a doc that <link>s a separate .rcss, real inotify event,
wl_event_loop pumped, assert the document actually reloaded) — fails on the buggy
code, passes now; no more relying on the seam.
Real-seat verified: editing dock.rcss now reloads the live dock (border-radius +
background-color changes apply on save). kernel 59 cases/260 assertions green on
build + build-asan, no new suppressions. Edits confined to packages/kernel/.
|
|
The hot-reload watcher was substrate-internal; expose it as a typed RAII primitive
any extension can use (config hot-reload is the first consumer), per "the kernel
owns the event/service bus; extensions never hold raw event-loop glue".
- New public watch.hpp: `class FileWatch` (move-only RAII; ~/reset() stop the
watch) + `Host::watch_file(path, on_change) -> FileWatch`. on_change fires on the
event-loop thread, COALESCED (one save = one call), EDITOR-SAFE (dir-watch the
basename across temp+rename), fires on CREATE of a not-yet-existing file, and is
ERROR-ISOLATED to the calling extension (carries its id; a throw disables only
that extension). UNGATED — works without UNBOX_DEV.
- New src/file_watcher.{hpp,cpp}: ONE session-wide inotify instance on the
wl_event_loop multiplexing all watched paths. The substrate's UI-asset hot-reload
was refactored onto it (no second inotify); only the substrate's *decision* to
watch UI assets stays UNBOX_DEV-gated. Created lazily on first watch; torn down
leak-clean before the loop dies.
host.hpp/kernel.md documented. kernel 58 cases/254 assertions green on build +
build-asan (incl. the inotify path), no new suppressions. Edits confined to
packages/kernel/.
|
|
Externalize UI documents so RML/RCSS design changes need no C++ recompile — and,
in dev, no restart.
- UiSurfaceSpec::rml_path now actually loads the document from a file (path wins
over rml_inline, as documented). Resolution: absolute path as-is; relative path
against $UNBOX_ASSET_DIR, else the compile-time UNBOX_ASSET_DIR_DEFAULT (the
install data dir), else cwd. The document URL is set so its <link> RCSS / asset
refs resolve relative to the doc's own dir. Missing/unreadable file -> nullptr
(degrade, never throw).
- Dev hot-reload (gated by $UNBOX_DEV): an inotify watcher integrated into the
wl_event_loop (never blocks) watches the asset DIRS (dir-watch for IN_CLOSE_WRITE
/ IN_MOVED_TO, since editors save via temp+rename), coalesces events, and on a
change to a surface's backing .rml/.rcss reloads the document IN PLACE:
ClearStyleSheetCache + UnloadDocument + reload, preserving the surface's RmlUi
context, data model and the extension's registered bind_*/bind_list* getters
(the extension does NOT re-register), and its geometry/visibility; preview
textures are kept. A malformed file on reload is ERROR-ISOLATED — the previous
good document keeps rendering, one warning is logged, and a later good save
recovers; the session never dies.
- Test seam Server::ui_reload_surface() drives reload deterministically.
ui.hpp documents rml_path + the dev hot-reload behavior. kernel 54 cases/232
assertions green on build + build-asan (incl. the UNBOX_DEV inotify path), no new
suppressions. Edits confined to packages/kernel/.
|
|
Two substrate capabilities the stage dock forced (both verified real-seat nested
and on the gles2 headless path):
1. Per-pixel alpha. A ui surface composited opaque, so any overlay (the dock)
occluded the toplevels beneath it. Root cause: a stray opaque
render_iface->Clear() (glClearColor 0,0,0,1) in render_surface overrode the
transparent BeginFrame clear, and EndFrame's premultiplied composite carried
the opaque base to the buffer. Fix: drop the stray Clear(); clear the OUTPUT
FBO to (0,0,0,0) once before BeginFrame. Blend was already correct
premultiplied; the substrate never sets an opaque region (now guarded by a
probe); ARGB8888 alpha survives end to end. A document whose <body> is
transparent now shows the scene through its un-painted pixels.
2. set_size resizes the render target. Previously logical-only (the slice-5
documented change-request): set_size re-laid-out the RmlUi document but did
NOT realloc the GL target, so a surface created small and grown rendered into
its original buffer (the dock, created as a 1px placeholder and grown on
minimize, was invisible). Fix: set_size now reallocs the FBO + dmabuf
swapchain/shm + EGLImage + texture + scene buffer on an ACTUAL size change
(no-op same-size, cheap; set_position still cheap). Grow and shrink both
render fully; alpha/upright-flip/blend/fence-sync preserved.
ui.hpp documents both. kernel 45 cases/182 assertions green on build + build-asan
(no new suppressions). Edits confined to packages/kernel/.
|
|
The stage dock is one RML document rendering a variable list of slots (one per
minimized window). Adds the deferred slice-6 list-binding shape to UiSurface:
bind_list(name, count) + typed per-row fields bind_list_string/int/double/bool
(list, field, getter(row)) read as {{ row.field }} via data-for, and
bind_list_event(list, event, callback(row)) routed from data-event-*(it_index).
dirty(<list>) re-reads count + visible rows. Same error-isolation + bind-before-
first-frame contract as the scalar bindings; nested lists unsupported.
kernel suite green on build + build-asan (asan clean). Edits confined to packages/kernel/.
|
|
The keystone for the stage dock. Proves Fork B on the real target (Mesa crocus,
HD 4400): a toplevel's pixels, rendered by the wlr GLES2 renderer into a LINEAR
ARGB8888 dmabuf, import as an EGLImage -> sampled GL texture in the sibling RMLUi
GLES 3.2 context (the slice-3 bridge run in reverse) and composite into an
<img src="unbox-preview://N"> inside a ui surface — upright, color-correct.
Public surface (ui.hpp): class Preview (source_uri/source_width/source_height/
refresh) + UiSubstrate::create_preview(wlr_scene_tree*) -> unique_ptr<Preview>
(nullptr if no GL path; never throws). Kernel-suite probes: ui_preview_import_is_dmabuf,
ui_pixel(x,y). Clean four-resource teardown (URI reg, GL texture, EGLImage, dmabuf);
refresh-after-source-destruction is UB (consumer drops Preview on unmap).
kernel 42 cases/150 assertions green on build + build-asan (asan clean, no new
suppressions). Edits confined to packages/kernel/.
|
|
Intercept the XF86Switch_VT_1..12 keysyms before the keybinding filter and call
wlr_session_change_vt, so the user can always switch consoles while unbox runs.
Clean no-op without a session (headless/nested). Pure vt_for_keysym helper +
doctest; wlroots reached via the wlr.hpp wrapper.
Real-seat verified on the CF-AX3.
|
|
exports WAYLAND_DISPLAY
ext-keybindings (new core ext) reads unbox.toml: tap-Super spawns fuzzel,
Alt+Tab/Alt+Shift+Tab rotate focus across all toplevels, plus Alt+F1 and
Ctrl+Alt+Backspace (quit). ext-xdg-shell's hardcoded keybinds removed
(migrated to the toml).
Kernel setenv()s WAYLAND_DISPLAY at startup so extension-spawned clients
connect to unbox, not the launching session — fixes fuzzel "no monitors"
on the real seat.
build + build-asan green: third-party Mesa/EGL/DRM + vendored-RmlUi sanitizer
noise suppressed (suppressions/), our code stays leak-checked; a real
libwayland leak in the layer-shell client test fixed.
Harness: spawn-env + sanitizer-noise rules, diagnose-real-seat skill,
GLOSSARY keybinding/action/tap-binding. Real-seat verified on the CF-AX3.
|
|
The ui substrate is now the extension-facing contract (unbox/kernel/ui.hpp):
Host::ui() -> UiSubstrate::create_surface(spec) -> UiSurface with typed
scalar bindings (int/double/bool/string getters), data-event callbacks
(error-isolated per extension), dirty(), geometry/visibility — RMLUi and
GL stay kernel-private. Production sync: glFinish replaced by
EGL_KHR_fence_sync + 2-deep wlr_swapchain. ui_spike retired (orientation
guard + dirty-cycle coverage live on as substrate tests).
Input: ONE kernel routing path feeds pointer AND touch into ui surfaces
with consume-or-pass semantics and implicit-grab ownership (the consumer
of a press owns the matching release; per touch point too) — fixes
drag-release-over-ui sticking. touch-mode: state machine + debounce +
on_touch_mode_changed notification, NO visual scaling (user decision
after hardware hands-on; dp-ratio stays 1.0; see plan §2).
ext-xdg-shell: GrabMachine generalized to pointer-OR-touch interaction
source (touch titlebar drag works; originating-point pinning); fixed the
seat implicit-grab leak (suppressed release after forwarded press
swallowed all later touch-downs — pointer/touch alternation doctested);
factory renamed create(). ext-layer-shell: on_demand keyboard
interactivity via scene hit resolution. host-bin: --ui-demo extension
(temporary acceptance demo on the public contract, dies in slice 6).
User hands-on verified: same surface by mouse and finger, tap counter,
touch-mode neutrality, no click-through, drag alternation, fuzzel
on_demand. 113 doctest cases green, ASan/UBSan clean (our code), idle
RSS ≈78 MiB. Harness: UX-feel hands-on lesson (ORCHESTRATOR §2.6),
nested-run pkill/setsid notes, touch-mode glossary redefinition.
|
|
extensions
The kernel now names NO concrete feature. It owns: the extension host
(install/topological activate, missing-dep/cycle = startup error), the
typed Event/Filter bus (error-isolated: a throwing extension is disabled,
never the session; RAII Subscriptions), the Host API (per-extension
facade: borrows, scene layers, event catalogue, typed services), the
public RAII Listener, and a typed surface→scene-tree registry
(Host::host_surface/scene_tree_for) that replaced the untyped
wlr_surface.data convention both extensions flagged.
- ext-xdg-shell (core): toplevel/popup lifecycle, focus-on-map,
click/tap-to-focus, pointer/touch routing incl. button+axis (the
kernel only moves the cursor and emits — a contract-doc lie caught by
user hands-on), interactive move/resize via pure GrabMachine (fixes
the request-arrives-after-release race: grab requires request ∧
button-down, release always ends it), Alt+F1 cycle,
Ctrl+Alt+Backspace terminate (labwc's default A-Escape=Exit killed
the dev session once — never again; see nested-run skill).
- ext-layer-shell (core): wlr-layer-shell v1 (proto v5) for external
clients; pure doctest-hard arrangement core; fuzzel verified visually
nested (fix: seed outputs from output_layout at activate — events-only
tracking missed pre-activation outputs; plus a scene-node double-free).
- First protocol codegen: vendored wlr-layer-shell XML + wayland-scanner
server-header propagated through kernel_dep; wlr.hpp grew a
namespace→_namespace keyword fix for the generated header.
- Glossary: 'scene layer' (user-approved). New rules earned:
parallel-wave-builds, contract-docs.
- User hands-on verified: typing, click-to-focus, drag-select, scroll,
titlebar drag-move (slow + flick), Alt+F1, fuzzel + arrows, touch tap,
Ctrl+Alt+Backspace. 68 doctest cases green, ASan/UBSan clean (our
code), idle RSS ≈73 MiB.
|
|
Plan A verified on hardware (HD 4400/crocus): RMLUi renders into a GLES
3.2 sibling-context FBO backed by a dmabuf wlr_buffer (wlr_allocator +
EGLImage import), composited as a wlr_scene_buffer with per-frame damage.
Plan B (glReadPixels→shm) implemented and verified as runtime fallback;
auto-engages when any Plan-A precondition fails. Plan C not needed.
- Hello-world RML doc: text, data-bound frame counter, pointer input
proof (hover/:active) — verified upright on screen via screenshot after
fixing the classic FBO Y-flip (buffer-level V-flip keeps display ==
document coords for input); position-aware orientation guard added.
- Temporary spike surface: Options::ui_spike + frame-count/orientation
probes, host-bin --ui-spike flag; replaced by the real ui substrate
contract in slice 4+.
- kernel suite 6 cases / 416 assertions green; ASan/UBSan clean in our
code (Mesa leak noise + 2 benign UBSan downcast reports inside vendored
RMLUi are known); idle RSS ≈83 MiB.
- Deferred (notes/plan.md §7): glFinish→EGL fence + swapchain; dmabuf
render-format negotiation (private API in wlroots 0.20).
|
|
touch added
Server contract (pimpl, create/run/dispatch/terminate) over a faithful
tinywl 0.20.1 port: outputs via wlr_scene, xdg-shell toplevels+popups,
focus, interactive move/resize, keyboard/pointer through wlr_cursor — plus
touch (down/up/motion/cancel/frame via seat notifies with per-point origin
tracking), which tinywl lacks. RAII Listener replaces manual wl_list_remove
bookkeeping; shutdown ordering documented in kernel.md. xkbcommon added as
a system dep. Verified: nested under labwc (output WL-1, foot mapped and
focused on GLES2) and a headless+pixman boot test in the kernel suite.
|
|
vendored
Root meson.build (C++23, WLR_USE_UNSTABLE, ccache-detected) with RMLUi 6.2
as a wrap-file tarball built through the cmake module (no git submodules —
settled decision) and doctest 2.5.2 from wrapdb. kernel unit: extern-"C"
wlr.hpp wrapper (with the C99 [static N] array-param workaround documented
in kernel.md), slice-1 probe contract, doctest suite (1/1 green). host-bin:
composition root printing versions, exit 0. tasks.md slice 1 done.
|