| Age | Commit message (Collapse) | Author |
|
surfaces
Three additive, kernel-internal capabilities (no extension-facing signature
changes beyond the documented UiSurfaceSpec field + SurfaceElement-unrelated probes):
- Screenshots (grim): create wlr_screencopy_manager_v1 + wlr_xdg_output_manager_v1
in init() alongside the existing compositor/data-device globals (policy-free
plumbing; wlroots wires them to the kernel-owned outputs/renderer). The captured
image is the standard wlr_scene_output_commit composite, so RML-composited
documents (scene-buffer nodes) are captured correctly. wlr.hpp gains the two
headers (static-blanking re-audited inert).
- Arbitrary raster image decode: vendor stb_image (single-header, public domain;
warnings isolated to its own warning_level=0 TU) and extend the RMLUi
RenderInterface LoadTexture (was uncompressed-TGA-only) to decode PNG/JPEG/BMP/
GIF/TGA via stbi_load_from_memory (RGBA, no BGR swizzle), falling back to the
legacy TGA path. A SubstrateSystemInterface JoinPath override stops RmlUi
stripping the leading '/' of an absolute source during URL resolution, so both
<img src='/abs'> and decorator: image('/abs') load the same file. Deterministic
ui_pixel readback tests (<img> + decorator paths, red/blue PNG fixtures).
- input-transparent ui surfaces: UiSurfaceSpec gains 'bool input_transparent'
(default false). When true the surface still composites but is skipped by the
press-ownership hit test, so it never steals pointer/touch from windows above it
-- required for a full-screen background (wallpaper). Deterministic seam test
proves a transparent surface does not consume a press while an opaque one does.
|
|
xdg-decoration
- SurfaceElement::rendered_width()/height(): read back the RCSS-resolved root
<img> content box (the substrate already computes it for popup placement) so a
window manager can size a client to its on-screen tile. Reading geometry, not
computing it -- RCSS still owns layout.
- UiSurface::bind_list_drag(list, name, cb(row, phase, x, y)): the per-row drag
binding (list analogue of bind_drag + bind_list_event), for per-window move and
resize in a data-for list.
- Expose wlr_xdg_decoration_v1 through the wlr wrapper (for ext-xdg-shell's
server-side-decoration negotiation).
|
|
Diagnosed live (nested unbox + grim screenshots). Two bugs stopped any window
from showing; both fixed and visually confirmed (multi-window, live-updating,
focus highlight, RCSS tiling).
1. kernel (ui_substrate.cpp, adopt_node) — STABLE TEXTURE ID across re-imports.
The live import did glDeleteTextures+glGenTextures every commit, minting a NEW
GL id each frame. But RmlUi's image()/<img> decorator caches the texture HANDLE
it gets from LoadTexture(uri) ONCE, so after the first re-import it drew a
DELETED texture -> blank. Now node.tex is allocated ONCE and just re-pointed at
the new EGLImage (dmabuf) / re-uploaded (shm) on each re-import, destroying the
previous EGLImage after the rebind. The id stays constant => RmlUi's cached
handle stays valid => the window updates live. (Reimport counting unchanged.)
2. ext-window-field assets — the window texture is painted via an RCSS image()
DECORATOR bound with data-style-decorator (the stage-dock pattern), NOT an
<img src="{{ w.live_uri }}">: RmlUi does not substitute a data binding inside
an img src, so the literal "{{ w.live_uri }}" was reaching the texture loader
("Could not load texture"). Also `contain` (was `cover`) so the whole window
shows instead of cropping its center.
Verified: foot composites as a surface element in the window field and updates
live (a ticking clock); a 2nd window tiles with the focused one highlighted.
kernel + ext-window-field suites green; build-asan kernel green (no leak/UB).
Follow-up: a kernel regression test asserting the texture id is stable across
re-imports (no test caught this — RmlUi caching is the subtlety).
|
|
Completes the window-manager input story.
kernel (additive): SurfaceElement::on_pressed(std::function<void()>) — the
substrate invokes it (error-isolated to the owner) when a pointer button PRESS
or touch DOWN is routed to that surface element (root or any subsurface/popup
child fires the root element's handler), in addition to the existing client
input-back forwarding. Not fired on motion/release/miss. It is the
click/tap-to-focus SIGNAL; focus policy stays the wm's.
ext-window-field: on map, sets the element's on_pressed -> that toplevel's
Toplevel::focus() (keyboard focus + on_toplevel_focused -> RCSS raise/highlight),
guarded on the window still being tracked. So a click/tap on a background window
now focuses it (the gap noted in Wave 3).
kernel + ext-window-field + ext-xdg-shell suites green; build-asan green, no new
unbox::-framed leak/UB (the stored handler dies with the element).
|
|
Extends the live SurfaceElement to the whole surface TREE and routes input
back to clients.
Surface trees:
- create_surface_element(root) now manages subsurfaces + xdg popups as
per-subsurface child elements (unbox-surface://N.K), each its own live
seq-gated texture at its tree offset; the substrate re-walks the live tree
each dirty tick (wlr_surface_for_each_surface + popup walk), reconciling by
wl_surface identity and dropping a node the instant it leaves the tree.
- Frame-callback duty now walks the WHOLE tree per composited frame.
- Parent-relative child placement (place_child_box, pure core): child <img>
positioned relative to the root img's resolved box, so a moving/resized
parent drags its children; popups unclipped. Caller tracks only the root.
Input-back (automatic; the wm wires no seat calls):
- pure core src/input_core.hpp (port of the spike's spike_input_core):
project_to_screen/unproject_to_local + place_child_box, doctested
(criterion-3 round-trip < 0.01px; affine exact).
- a pointer/touch pick landing on a surface-element node maps the point through
the node img's real RCSS transform via Element::Project, then box->surface-
local, and forwards to that client via wl_seat at surface-local px. Normal
RML picks still fire bind_event/bind_drag unchanged. Cursor stays a wlr plane.
- SurfaceElement::focus_keyboard() (new public method): seat keyboard-focus
MECHANISM only (focus POLICY is the window-field wave); cleared on destroy.
Tests: pure-core doctests + a headless test driving a REAL in-process client
(toplevel root + subsurface + xdg popup): tree composes as >=3 child <img>,
whole-tree frame-done, pointer enter/motion/button + touch at expected surface-
local coords (incl. a rotateY(35) transformed-element case proving Project),
keyboard enter+key. kernel suite 72c/375a green; build-asan 0 records (the
seat/per-node-import/listener lifetimes clean). Spike untouched.
|
|
Phase 2 Wave 1: the live sibling of Preview. A SurfaceElement is backed by a
client wl_surface's current committed buffer, imported zero-copy into the RMLUi
sibling GLES context and served under an unbox-surface://N URI, shown via
<img src> in any UiSurface.
Public contract (ui.hpp):
- class SurfaceElement { source_uri(); width(); height(); } (no refresh()).
- UiSubstrate::create_surface_element(wlr_surface* client) -> unique_ptr;
nullptr on no-GL/import-fail, never throws; `client` is a borrow the caller
must outlive and drop on unmap/destroy (lifetime documented per
listener-lifetime).
Behavior (ported from the in-tree spike, untouched):
- seq-gated re-import (wlr_surface->current.seq), pool-reuse-proof; double-
buffered wlr_buffer_lock/unlock (<=1 pinned, balanced incl. prev==buf).
- frame-callback duty per composited frame so the client keeps drawing (the
stuck-frame fix); a frame stays scheduled while >=1 element exists.
- commit dirties the hosting ui surface (dirty-gate); static client = no work.
- shm-upload + R<->B swizzle fallback when there is no dmabuf path.
Pure-core predicate (surface_element_needs_reimport) doctested; headless
integration test drives a REAL in-process Wayland client (re-import on seq
advance, the pooled same-pointer case, zero idle re-imports, climbing
frame-done). kernel suite + build-asan green on Haswell+crocus. Test-only
wayland-client dep (kernel-tests scope; user-accepted). Scope held: single
surface, no input-back/damage/scene changes (later waves).
|
|
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.
|
|
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/.
|
|
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.
|