summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/server.cpp
AgeCommit message (Collapse)Author
2026-06-15kernel: screenshot protocols + arbitrary-image decoding + input-transparent ↵Adam Malczewski
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.
2026-06-15rml-compositing W3: click/tap-to-focus (kernel on_pressed + window-field)Adam Malczewski
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).
2026-06-15kernel(rml-compositing): fix Wave-1 test-seam listener lifetime (root cause)Adam Malczewski
The Wave-1 headless surface-capture test seam attached a commit Listener to every client wl_surface and only detached at Server teardown, so a client destroying a surface mid-session tripped wlroots' wl_list_empty(&surface->events.commit.listener_list) assertion (SIGABRT). This aborted ext-stage-dock-glue at teardown and forced an in-unit workaround in ext-xdg-shell-client. Fix: the per-surface commit Listener is now RAII-scoped to the surface's own lifetime (dropped on a wlr_surface.events.destroy listener), so it unsubscribes before the surface resource is destroyed. Test-probe behavior unchanged; extension-facing contract unchanged. kernel suite green; ext-stage-dock now GREEN (no teardown SIGABRT); ext-xdg-shell still green; build-asan kernel green, no new unbox::-framed leak/UB.
2026-06-15kernel(rml-compositing W1b): surface trees + input-back + keyboard-focusAdam Malczewski
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.
2026-06-15kernel(rml-compositing W1): live SurfaceElement (zero-copy, self-updating)Adam Malczewski
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).
2026-06-14kernel: add request_frames() frame callback + UiSurface::transition_timing()Adam Malczewski
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.
2026-06-14kernel: add UiSurface::bind_drag (RmlUi drag events with surface-local coords)Adam Malczewski
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.
2026-06-13kernel: generalize the inotify watcher into a Host::watch_file serviceAdam Malczewski
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/.
2026-06-13kernel: load ui surfaces from RML asset files + dev hot-reloadAdam Malczewski
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/.
2026-06-13kernel: ui surfaces composite with per-pixel alpha + set_size resizes the targetAdam Malczewski
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/.
2026-06-13Slice 10 b2: UiSurface list/container data bindingsAdam Malczewski
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/.
2026-06-13Slice 10 a1: preview pipeline spike — wlr pixels -> RMLUi texture (Fork-B GO)Adam Malczewski
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/.
2026-06-13Kernel: Ctrl+Alt+F1..F12 VT switching (session escape hatch)Adam Malczewski
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.
2026-06-13Slice 5b: config-driven keybindings — Super→fuzzel, Alt+Tab; kernel ↵Adam Malczewski
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.
2026-06-13Slice 5: real ui substrate + unified input routing + touch-mode; spike retiredAdam Malczewski
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.
2026-06-12Slice 4: extension host + typed bus; xdg-shell/layer-shell extracted to core ↵Adam Malczewski
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.
2026-06-12Slice 3: THE SPIKE — RMLUi→wlr_scene bridge lands, GOAdam Malczewski
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).
2026-06-12Slice 2: tinywl port — kernel compositor runs nested, manages toplevels, ↵Adam Malczewski
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.