summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-12 19:11:59 +0900
committerAdam Malczewski <[email protected]>2026-06-12 19:11:59 +0900
commita21f705692595ea711a736e2ae9c256c1dde7b1e (patch)
tree2cb3e6380e65f9b584a1dea1cd43e1a127a4f56d
parent6f45dc177540d6c6ae7596427209091d4c7adc20 (diff)
downloadunbox-a21f705692595ea711a736e2ae9c256c1dde7b1e.tar.gz
unbox-a21f705692595ea711a736e2ae9c256c1dde7b1e.zip
Slice 1: Meson skeleton — kernel links wlroots 0.20 from C++, RMLUi 6.2 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.
-rw-r--r--.clang-format8
-rw-r--r--meson.build35
-rw-r--r--notes/plan.md3
-rw-r--r--packages/host-bin/meson.build8
-rw-r--r--packages/host-bin/src/main.cpp14
-rw-r--r--packages/kernel/include/unbox/kernel/kernel.hpp26
-rw-r--r--packages/kernel/include/unbox/kernel/wlr.hpp28
-rw-r--r--packages/kernel/kernel.md19
-rw-r--r--packages/kernel/meson.build26
-rw-r--r--packages/kernel/src/kernel.cpp26
-rw-r--r--packages/kernel/tests/test_kernel.cpp13
-rw-r--r--subprojects/doctest.wrap10
-rw-r--r--subprojects/rmlui.wrap6
-rw-r--r--tasks.md6
14 files changed, 224 insertions, 4 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..76d4ae5
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,8 @@
+# Minimal seed (plan.md §7: refine on first real dispute, not before).
+BasedOnStyle: LLVM
+IndentWidth: 4
+ColumnLimit: 100
+PointerAlignment: Left
+DerivePointerAlignment: false
+AccessModifierOffset: -4
+AllowShortFunctionsOnASingleLine: Inline
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..3c297fe
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,35 @@
+project(
+ 'unbox',
+ 'cpp',
+ version: '0.0.1',
+ meson_version: '>=1.4.0',
+ default_options: [
+ 'cpp_std=c++23',
+ 'warning_level=2',
+ 'default_library=static',
+ ],
+)
+
+# wlroots is pre-1.0: every release breaks API. The pin lives HERE and in
+# the kernel's wlr.hpp wrapper — nowhere else (.unbox/rules/wlroots-include.md).
+add_project_arguments('-DWLR_USE_UNSTABLE', language: 'cpp')
+
+wlroots_dep = dependency('wlroots-0.20')
+wayland_server_dep = dependency('wayland-server')
+
+# RMLUi: CMake-only upstream, vendored as a wrap-file tarball (NO git
+# submodules — settled decision, notes/plan.md §2) built via the cmake module.
+cmake = import('cmake')
+rmlui_opts = cmake.subproject_options()
+rmlui_opts.add_cmake_defines({
+ 'BUILD_SHARED_LIBS': false,
+ 'RMLUI_SAMPLES': false,
+})
+rmlui_proj = cmake.subproject('rmlui', options: rmlui_opts)
+rmlui_dep = rmlui_proj.dependency('rmlui_core')
+
+doctest_dep = dependency('doctest')
+
+# Units. Adding one? ALL FOUR steps of .unbox/rules/unit-registration.md.
+subdir('packages/kernel')
+subdir('packages/host-bin')
diff --git a/notes/plan.md b/notes/plan.md
index 061eb6a..da0da54 100644
--- a/notes/plan.md
+++ b/notes/plan.md
@@ -50,7 +50,8 @@ solves), and the trigger that would reopen it.
| **wlroots 0.20**, system package, pinned, via ONE extern-"C" wrapper header | Matches running labwc (proven on this hardware); wlroots API churns between minors — the pin + single wrapper contains upgrades to one file | 0.21+ needed for a feature; bump = one dedicated slice |
| **wlr GLES2 renderer** for compositing; **sibling GLES 3.2 EGL context** (shared EGLDisplay) for RMLUi | wlroots' renderer is its own battle-tested choice; RMLUi's maintained backend needs GL3/GLES3; GLES 3.2 verified native. Older GL API ≠ faster: same crocus driver either way, and ES3 features (UBOs, VAOs, instancing) REDUCE CPU-side driver overhead — the actual bottleneck on this CPU | — |
| **NO ANGLE / translation layers** | ANGLE's Linux backend is Vulkan (no driver here; hasvk deprecated); ANGLE-on-GL just stacks on the Mesa we'd bypass; Chromium-scale build; tens of MB RSS; wlroots speaks system EGL | — |
-| **Meson + Ninja**, RMLUi via Meson's cmake subproject module; ccache; `build/` + `build-asan/` separate | Wayland-ecosystem native (wlroots/labwc/sway), first-class wayland-scanner codegen; RMLUi is CMake-only, the cmake module bridges it, built once | cmake-module friction exceeds ~a day of fighting → fall back to prebuilding RMLUi as a system-installed lib |
+| **Meson + Ninja**, RMLUi via Meson's cmake subproject module; ccache; `build/` + `build-asan/` separate | Wayland-ecosystem native (wlroots/labwc/sway), first-class wayland-scanner codegen; RMLUi is CMake-only, the cmake module bridges it, built once. PROVEN at slice 1: RMLUi 6.2 configures + builds via the cmake module first try | cmake-module friction on a future RMLUi bump → fall back to prebuilding RMLUi as a system-installed lib |
+| **Vendoring = Meson wrap-file tarballs, NEVER git submodules** (user decision) | Wraps pin by sha256, sources land in gitignored `subprojects/`, `packagecache/` allows pre-seeding; submodule UX rejected | — |
| **C++23, gcc 16** | Already installed; designated initializers ease wlroots struct setup; Hyprland proved years of C++-on-wlroots | — |
| **toml++** for `unbox.toml` config | Mirrors dispatch.toml convention; header-only | — |
| **doctest** for pure cores | Lightest compile cost of the mainstream frameworks — compile time IS the scarce resource here | — |
diff --git a/packages/host-bin/meson.build b/packages/host-bin/meson.build
new file mode 100644
index 0000000..3ff936a
--- /dev/null
+++ b/packages/host-bin/meson.build
@@ -0,0 +1,8 @@
+# host-bin — the composition root (orchestrator-owned). The ONLY place that
+# names every extension; activates them in dependency order (from slice 4).
+
+executable(
+ 'unbox',
+ 'src/main.cpp',
+ dependencies: [kernel_dep],
+)
diff --git a/packages/host-bin/src/main.cpp b/packages/host-bin/src/main.cpp
new file mode 100644
index 0000000..713186d
--- /dev/null
+++ b/packages/host-bin/src/main.cpp
@@ -0,0 +1,14 @@
+#include <unbox/kernel/kernel.hpp>
+
+#include <cstdio>
+
+auto main() -> int {
+ const bool probe_ok = unbox::kernel::link_probe();
+
+ std::printf("unbox 0.0.1 — kernel skeleton (slice 1)\n");
+ std::printf(" wlroots %s\n", unbox::kernel::wlroots_version().c_str());
+ std::printf(" RmlUi %s\n", unbox::kernel::rmlui_version().c_str());
+ std::printf(" link probe %s\n", probe_ok ? "ok" : "FAILED");
+
+ return probe_ok ? 0 : 1;
+}
diff --git a/packages/kernel/include/unbox/kernel/kernel.hpp b/packages/kernel/include/unbox/kernel/kernel.hpp
new file mode 100644
index 0000000..9836a14
--- /dev/null
+++ b/packages/kernel/include/unbox/kernel/kernel.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <string>
+
+// Slice-1 probe surface: proves the kernel compiles against wlroots from
+// C++ and links wlroots, libwayland-server, and the vendored RMLUi. The
+// real contracts (extension host, bus, scene/seat glue, ui substrate)
+// replace this from slice 2 on.
+//
+// Calling context: everything in unbox runs on the single wl_event_loop
+// thread unless a contract explicitly states otherwise.
+
+namespace unbox::kernel {
+
+/// The wlroots version this kernel was compiled against (the 0.20 pin).
+[[nodiscard]] auto wlroots_version() -> std::string;
+
+/// The RMLUi version linked from the vendored subproject.
+[[nodiscard]] auto rmlui_version() -> std::string;
+
+/// Creates and immediately destroys a wl_display: proves we can call into
+/// libwayland-server/wlroots at runtime, not just link. Returns true on
+/// success. No side effects beyond wlroots log initialization.
+[[nodiscard]] auto link_probe() -> bool;
+
+} // namespace unbox::kernel
diff --git a/packages/kernel/include/unbox/kernel/wlr.hpp b/packages/kernel/include/unbox/kernel/wlr.hpp
new file mode 100644
index 0000000..6374a52
--- /dev/null
+++ b/packages/kernel/include/unbox/kernel/wlr.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+// The ONLY file in the project allowed to include wlroots / libwayland
+// headers (.unbox/rules/wlroots-include.md). wlroots is C; this wrapper
+// provides the extern-"C" guards C++ needs. WLR_USE_UNSTABLE is defined
+// project-wide in the root meson.build; the version pin is wlroots 0.20.
+//
+// Grow this include list as kernel glue needs more types — never include
+// <wlr/...> anywhere else, in any unit.
+
+extern "C" {
+#include <wayland-server-core.h>
+
+// wlroots headers use C99 array-parameter syntax (`float color[static 4]`),
+// which is invalid C++. Blanking `static` around the wlr includes is the
+// proven workaround (Hyprland shipped years on it): `static inline` header
+// helpers become plain `inline`, which C++ ODR-merges safely. Keep the
+// #define scoped to EXACTLY these includes.
+#define static
+#include <wlr/backend.h>
+#include <wlr/render/allocator.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_output.h>
+#include <wlr/types/wlr_scene.h>
+#include <wlr/util/log.h>
+#include <wlr/version.h>
+#undef static
+}
diff --git a/packages/kernel/kernel.md b/packages/kernel/kernel.md
new file mode 100644
index 0000000..e2ee684
--- /dev/null
+++ b/packages/kernel/kernel.md
@@ -0,0 +1,19 @@
+# kernel — package notes
+
+Slice-1 state: a probe surface (`kernel.hpp`) proving C++ ↔ wlroots ↔ RMLUi
+compile/link. Real contracts (extension host, bus, scene/seat glue, ui
+substrate) land from slice 2.
+
+Gotchas the headers can't express:
+
+- **`wlr.hpp` blanks `static` around the wlr includes.** wlroots headers
+ use C99 array-parameter syntax (`float color[static 4]`), invalid in
+ C++. With `static` blanked, `static inline` helpers become `inline`
+ (ODR-merged, safe). Cost: a function-local `static` inside a header
+ inline would silently lose persistence — none exist in our include set;
+ re-audit when ADDING includes to the wrapper.
+- **RMLUi is kernel-private.** `rmlui_dep` is deliberately absent from
+ `kernel_dep` propagation (see meson.build): extensions contribute RML
+ documents + data bindings via the ui substrate, never RMLUi API calls.
+ Do not "fix" a missing-RMLUi-header error downstream by propagating it.
+- Everything runs on the single `wl_event_loop` thread.
diff --git a/packages/kernel/meson.build b/packages/kernel/meson.build
new file mode 100644
index 0000000..4a67ced
--- /dev/null
+++ b/packages/kernel/meson.build
@@ -0,0 +1,26 @@
+# kernel — the minimal runtime core. Public headers (the ABI): include/unbox/kernel/
+
+kernel_inc = include_directories('include')
+
+kernel_lib = static_library(
+ 'unbox-kernel',
+ 'src/kernel.cpp',
+ include_directories: kernel_inc,
+ dependencies: [wlroots_dep, wayland_server_dep, rmlui_dep],
+)
+
+# What consumers get. wlroots/wayland propagate because wlr.hpp is a public
+# header; RMLUi does NOT — it is kernel-private (the ui substrate owns it,
+# extensions contribute RML documents + data bindings, never RMLUi calls).
+kernel_dep = declare_dependency(
+ link_with: kernel_lib,
+ include_directories: kernel_inc,
+ dependencies: [wlroots_dep, wayland_server_dep],
+)
+
+kernel_test = executable(
+ 'kernel-tests',
+ 'tests/test_kernel.cpp',
+ dependencies: [kernel_dep, doctest_dep],
+)
+test('kernel', kernel_test, suite: 'kernel')
diff --git a/packages/kernel/src/kernel.cpp b/packages/kernel/src/kernel.cpp
new file mode 100644
index 0000000..3de39c0
--- /dev/null
+++ b/packages/kernel/src/kernel.cpp
@@ -0,0 +1,26 @@
+#include <unbox/kernel/kernel.hpp>
+#include <unbox/kernel/wlr.hpp>
+
+#include <RmlUi/Core/Core.h>
+
+namespace unbox::kernel {
+
+auto wlroots_version() -> std::string {
+ return WLR_VERSION_STR;
+}
+
+auto rmlui_version() -> std::string {
+ return Rml::GetVersion();
+}
+
+auto link_probe() -> bool {
+ wlr_log_init(WLR_ERROR, nullptr);
+ wl_display* display = wl_display_create();
+ if (display == nullptr) {
+ return false;
+ }
+ wl_display_destroy(display);
+ return true;
+}
+
+} // namespace unbox::kernel
diff --git a/packages/kernel/tests/test_kernel.cpp b/packages/kernel/tests/test_kernel.cpp
new file mode 100644
index 0000000..ebcfbc3
--- /dev/null
+++ b/packages/kernel/tests/test_kernel.cpp
@@ -0,0 +1,13 @@
+#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+#include <doctest/doctest.h>
+
+#include <unbox/kernel/kernel.hpp>
+
+TEST_CASE("kernel compiles against and links wlroots + libwayland-server") {
+ CHECK(unbox::kernel::link_probe());
+ CHECK(unbox::kernel::wlroots_version().substr(0, 4) == "0.20");
+}
+
+TEST_CASE("vendored RMLUi subproject compiled and linked") {
+ CHECK(!unbox::kernel::rmlui_version().empty());
+}
diff --git a/subprojects/doctest.wrap b/subprojects/doctest.wrap
new file mode 100644
index 0000000..872898c
--- /dev/null
+++ b/subprojects/doctest.wrap
@@ -0,0 +1,10 @@
+[wrap-file]
+directory = doctest-2.5.2
+source_url = https://github.com/doctest/doctest/archive/refs/tags/v2.5.2.tar.gz
+source_filename = doctest-2.5.2.tar.gz
+source_hash = 9189960c2bbbc4f3382ce0773b2bb5f13e3afd8fed47f55f193e11e85a4f9854
+source_fallback_url = https://wrapdb.mesonbuild.com/v2/doctest_2.5.2-1/get_source/doctest-2.5.2.tar.gz
+wrapdb_version = 2.5.2-1
+
+[provide]
+dependency_names = doctest
diff --git a/subprojects/rmlui.wrap b/subprojects/rmlui.wrap
new file mode 100644
index 0000000..2d56578
--- /dev/null
+++ b/subprojects/rmlui.wrap
@@ -0,0 +1,6 @@
+[wrap-file]
+directory = RmlUi-6.2
+source_url = https://github.com/mikke89/RmlUi/archive/refs/tags/6.2.tar.gz
+source_filename = RmlUi-6.2.tar.gz
+source_hash = 814c3ff7b9666280338d8f0dda85979f5daf028d01c85fc8975431d1e2fd8e8b
+method = cmake
diff --git a/tasks.md b/tasks.md
index 3c1e933..14fa670 100644
--- a/tasks.md
+++ b/tasks.md
@@ -5,15 +5,15 @@
## Now
-**Next action:** Slice 1 — install toolchain (`sudo pacman -S meson ninja
-cmake ccache`), then bootstrap the Meson skeleton per `notes/plan.md` §5.
+**Next action:** Slice 2 — port tinywl into the kernel: backend/output/
+seat/scene glue, run nested under labwc, manage a foot toplevel.
## Slices
| # | Slice | Status | Acceptance |
|---|---|---|---|
| 0 | Harness skeleton | **DONE** 2026-06-12 | All harness md files in place |
-| 1 | Bootstrap: toolchain, Meson skeleton, RMLUi subproject compiles, empty kernel links wlroots-0.20 from C++ via the extern-"C" wrapper | pending | `ninja -C build` green; no-op binary runs + exits clean |
+| 1 | Bootstrap: toolchain, Meson skeleton, RMLUi subproject compiles, empty kernel links wlroots-0.20 from C++ via the extern-"C" wrapper | **DONE** 2026-06-12 | met: build green; tests 1/1; binary prints wlroots 0.20.1 + RmlUi 6.2, exits 0 |
| 2 | tinywl port: kernel skeleton runs nested under labwc | pending | opens as a window; manages a foot toplevel; keyboard/pointer/touch events flow |
| 3 | **THE SPIKE:** RMLUi→scene bridge | pending | a hello-world RML document composited as a scene node with damage tracking; go/no-go gate |
| 4 | Extension host + contracts: bus, manifests, static registration; xdg-shell/layer-shell refactored OUT of kernel into core extensions | pending | kernel names no feature; ext-xdg-shell + ext-layer-shell pass suite |