Skip to content

Latest commit

 

History

History
397 lines (340 loc) · 21.3 KB

platform-notes-linux.md

File metadata and controls

397 lines (340 loc) · 21.3 KB

Linux Platform Notes

backend/linux is a minimal native Linux host core with two windowing backends — Wayland (xdg-shell, default) and X11 (Xlib) — sharing one API. It uses the wzzc-dev/window@0.5.4-0.2.1 Linux package for event-loop and window handles, normalizes window/input events through the shared Event contract, and runs the Showcase entrypoints through the same renderer/runtime boundary as macOS and Windows. Application entrypoints supply ordered RendererProvider values for concrete rendering. backend/linux exposes the window package's Window::present_rgba_pixels presenter, raw-byte image I/O, and opaque native surface/display handles; Skia and WGPU construction stays in moui_skia_renderer and moui_wgpu_renderer.

The Wayland window path requests server-side decorations when the compositor exposes xdg-decoration. If the compositor falls back to client-side decorations, backend/linux reserves a small titlebar band above the MoUI content, draws the window title and basic controls into the renderer command stream, and translates input coordinates so application views still receive a content-origin coordinate space. The same adapter consumes the window package's Wayland key/modifier mapping and current pointer coordinates: Linux backend tests cover modifier propagation into shared keyboard events and button events using the position carried by the window event rather than stale pointer state. The fork also exposes Wayland data-device clipboard selection and file drag/drop events to MoUI; drag/drop paths continue through Event::DragDrop before reaching View::on_file_drop. Text-input focus state and IME requests are synchronized through the shared TextInputSession path used by other native hosts. That session now records TextInputImeRequestDiagnostics for each enabled/update request, including grapheme-normalized cursor/anchor character positions, UTF-8 offsets for surrounding text, the logical candidate-anchor caret rectangle, and whether surrounding-text payloads fit the window package's IME contract.

Windowing Backend Selection (Wayland / X11)

The window package selects its backend at startup before the MoUI host runs:

  1. MOUI_LINUX_WINDOWING=x11|wayland forces one backend.
  2. auto (default) prefers Wayland when WAYLAND_DISPLAY or XDG_SESSION_TYPE=wayland is set, uses X11 when only DISPLAY exists, and falls back to the other backend if the preferred one fails to connect.

The selection is logged to stderr as moui windowing backend: <backend>. @window_linux.EventLoop::windowing_backend() and Window::windowing_backend() expose the active backend to the host: moui/backend/linux uses it to skip client-side decorations (X11 uses window-manager decorations) and to steer renderer resolution — the X11 host binding omits the native display handle so the Skia hybrid provider rejects GPU surfaces and the CPU raster route is used. The X11 backend presents CPU raster frames through MIT-SHM (double-buffered) with an XPutImage fallback.

X11 slice limitations (matching docs/plans/active/linux-x11-backend.md): no GPU surface route, no IME, no XDND drag-and-drop, and clipboard is limited to the CLIPBOARD selection with UTF8_STRING targets.

On Wayland sessions without server-side decorations (GNOME mutter), the backend adds client-side window management: edge/corner zones start an interactive resize via xdg_toplevel.resize, and standard resize cursors are rendered through wp_cursor_shape_device_v1 when the compositor offers it. macOS hosts running the app inside a VM/SPICE guest should align the guest's org.gnome.desktop.peripherals.mouse natural-scroll with the host trackpad feel, since the host client translates trackpad gestures into wheel button events before the guest sees them.

Runtime Requirements

Linux runtime requirements are intentionally native:

  • On a Debian-family host, scripts/set_linux_deps.sh installs the package sets below in one step. The default set covers the Wayland window core, GLib/zlib link libraries, Weston for headless compositor checks, zenity, and the Skia renderer stack. --minimal restricts the install to the core Wayland build/runtime set, --with-webview adds WebKitGTK development packages for native WebView support, and --print-packages shows the exact apt list for the selected set before installing:

    sh scripts/set_linux_deps.sh                 # full default set (Skia included)
    sh scripts/set_linux_deps.sh --minimal       # core Wayland build/runtime only
    sh scripts/set_linux_deps.sh --with-webview  # + WebKitGTK native WebView
    sh scripts/set_linux_deps.sh --check         # verify the installed set

    The individual requirements below describe what each package set provides and how the MoUI prebuilds consume them.

  • A Wayland compositor, or an X server (desktop Xorg, Xvfb, or XWayland) when forcing or falling back to the X11 backend. For repeatable headless checks, run Weston with the headless backend and point WAYLAND_DISPLAY at its socket, or run Xvfb and point DISPLAY at it.

  • A usable Vulkan stack only when running WGPU diagnostics. Headless software validation can use Mesa llvmpipe through vulkan-swrast/Lavapipe when hardware Vulkan is not available.

  • Wayland development headers and generated xdg-shell protocol sources, plus X11 development headers (libx11-dev, libxext-dev, libxrandr-dev) for the wzzc-dev/window@0.5.4-0.2.1 native stubs.

  • wl_data_device_manager from the compositor for native clipboard selection and file drag/drop runtime behavior.

  • XDG desktop integration for Linux services: OpenURI goes through xdg-desktop-portal when available and falls back to the desktop opener; file-dialog selections use xdg-desktop-portal with zenity as the fallback dialog provider. Install zenity if portal is not available:

    sudo apt-get install zenity

    When neither portal nor zenity is available, file and folder selection silently returns cancelled, and the app prints a diagnostic message to stdout.

  • zlib / pthread / fontconfig system libraries for the final native link. moui/build.js owns the backend/linux zlib entry, moui_skia/build.js owns the Skia binding's pthread/fontconfig stack, and moui_wgpu_renderer/build.js owns moui_wgpu_renderer/fontconfig. Linux example entrypoints should not repeat -lz or fontconfig stacks; they only need an empty cc-link-flags override so Moon disables tcc -run when required.

  • glib-2.0 development headers and runtime library. backend/linux drives @services.TimerSource subscriptions through the GLib main loop (g_timeout_add / g_source_remove), so the moui prebuild resolves glib-2.0 through pkg-config and feeds the resulting -I include flags into stub-cc-flags and merges the libs into the backend/linux link_configs entry. On hosts where pkg-config cannot find glib-2.0, both resolve to empty (the C stub body is guarded by #ifdef __linux__ and only matters on Linux). Distro-specific setups can override the resolved flags with MOUI_LINUX_GLIB_STUB_CC_FLAGS and MOUI_LINUX_GLIB_CC_LINK_FLAGS.

  • WebKitGTK development packages (libwebkit2gtk-4.1-dev or 4.0) for native WebView support. The moui_webview prebuild auto-detects gtk+-3.0 with webkit2gtk-4.1 or webkit2gtk-4.0 through pkg-config; if found, it enables the native bridge. Fallback builds do not link WebKitGTK and report WebView unavailable. Distro-specific setups can override the detection with MOUI_LINUX_WEBKITGTK_STUB_CC_FLAGS and MOUI_LINUX_WEBKITGTK_CC_LINK_FLAGS.

Linux RISC-V64 Cross-build

MoUI's first Linux RISC-V64 path is an experimental architecture variant of the canonical linux/skia route. It targets riscv64-linux-gnu (glibc/LP64D) and deliberately selects Skia Raster with static linking. Vulkan, WGPU, WebView, and matching-device Wayland runtime claims are outside this first slice.

The fixture is locked to Ubuntu Base 24.04.4 RISC-V64 and Zig 0.16.0 in checks/toolchains/linux-riscv64.json. On a Linux host with qemu-user-static, prepare the sysroot and run the L0-L2 helper:

bash scripts/prepare-linux-riscv64-sysroot.sh \
  --output .cache/moui/riscv64/sysroot/ubuntu-24.04.4-riscv64
bash scripts/linux-riscv64-cross-build.sh \
  --sysroot .cache/moui/riscv64/sysroot/ubuntu-24.04.4-riscv64 \
  --run-qemu

The helper sets MOON_CC/MOON_AR to Zig wrappers, redirects pkg-config to the target sysroot, builds examples/showcase/linux_skia and the offscreen Skia renderer/text smokes in Moon Release mode, then verifies ELF64, the RISC-V machine, the LP64D glibc interpreter, static Skia, and the absence of a Vulkan dependency. The evidence directory includes the complete target package list, the sysroot file-checksum manifest, ELF reports, executable checksums, and smoke logs. --run-qemu chroots into the target rootfs for dynamic libraries, fontconfig, and fonts, and executes only renderer-owned offscreen smokes; it does not prove a Wayland compositor, input, IME, clipboard, or desktop service.

Cross-build details worth knowing when touching this route:

  • The Zig wrapper links against the sysroot libstdc++ (the Skia release archive is built with libstdc++, not zig's bundled libc++) and drops -lstdc++/-lc++ from link inputs so zig does not substitute its own libc++. It also strips the --sysroot prefix from absolute -L paths (zig re-adds it) and maps host-absolute library dirs (e.g. the Skia release cache) into the sysroot via stable symlinks.
  • moon links host-prebuilt x86_64 runtime objects (simdutf.o, moonbit_simdutf.o, libbacktrace.a) from the toolchain lib dir. The helper hides the simdutf objects and substitutes a small riscv64 libbacktrace implementation (scripts/moui-riscv64-libbacktrace-stub.c) for the duration of the cross build, restoring everything afterwards.
  • QEMU smokes run under a pseudo-terminal so MoonBit's buffered stdout is flushed per line; otherwise failure details printed immediately before fail_smoke's abort() are lost.
  • Set MOUI_RISCV64_QEMU_GDB=1 (and install gdb-multiarch) to run the text-emoji smoke under gdb via QEMU's gdbstub and print a backtrace at SIGABRT.
  • The locked sysroot includes fonts-noto-cjk: the text/emoji smoke renders CJK composition text, and without a CJK font fontconfig cannot fall back, leaving the composed line without dark pixels.

The helper self-test requires no downloaded sysroot and pins the explicit failure diagnostics for the wrong architecture/ABI, missing target .pc files, and accidental Vulkan enablement:

bash scripts/test-linux-riscv64-cross-build.sh

The architecture evidence contract lives in checks/architecture-evidence/linux-skia-riscv64.json. Keep its ready=false and runtimeL3.status=pending until a real RISC-V64 Wayland device produces matching-host evidence. A device run uses the existing Linux Showcase command and remains a separate L3 promotion step. Copy the cross-built Showcase ELF to the matching RISC-V64 Wayland device, then collect first-frame, input, IME, clipboard, and service logs with:

MOUI_SKIA_RENDERER=skia-raster ./linux_skia.exe

Running

Useful focused commands on a configured Linux host:

moon test moui/backend/linux --target native
moon build examples/showcase/linux_skia --target native
moon run examples/showcase/linux_skia --target native

The ordinary Linux Skia entrypoints are interactive app entrypoints. Keep matching-host first-frame smoke in tester/backend smoke runners and store those logs under ignored artifacts/ paths when they are needed for release notes.

When validating from a Linux VM mounted over the same checkout as a macOS or Windows host, keep native build output isolated. Either run moon clean before switching hosts or copy the checkout to a Linux-local temporary directory without _build; the native archive and MoonDB files are host-specific and can be corrupted by cross-host reuse.

WGPU Diagnostics

The WGPU diagnostic factory composes the Linux moui_wgpu_renderer/fontconfig provider with the shared Moon Cosmic fallback. The fontconfig provider includes real fontconfig family resolution, FreeType rasterization (loaded via dlopen), HarfBuzz shaping, embedded-font registration, and a narrow color-emoji path; MoonBit tests verify protocol versioning and native payload parsing on all platforms, while the full shaping/measurement/raster path runs on Linux with the required C libraries. Choose the engine through @wgpu_renderer.native(text_engine=...); the canonical linux_wgpu route uses the fontconfig provider with Moon Cosmic as its internal fallback.

Skia Renderer

Select the native mainline Skia renderer by importing wzzc-dev/moui_skia_renderer, adding @render_skia.from_env(platform=@render_skia.NativeGpuPlatform::Linux) to the app builder, and capturing LinuxHostAppOptions in @linux.entry. The provider binds a RendererSession backed by @render_skia.SkiaRasterRenderer and presents the CPU pixel frame through a narrow API exposed by wzzc-dev/window/linux. That window package owns the Wayland objects and provides Window::present_rgba_pixels, implemented with reusable wl_shm buffers, buffer-release tracking, wl_surface_attach, damage, commit, and display flush. Keeping the wl_shm presenter in the window backend avoids duplicating Wayland registry and buffer ownership in MoUI. Linux native WebView support is auto-detected via pkg-config. When WebKitGTK development packages are installed, the host syncs placements from DrawFrame.platform_views using the Wayland surface handle, offsets placement below client decorations when needed, pumps the GTK main context from the Linux event-loop wait path, forwards validated navigation/title/history/bridge events through the WebViewHost, and drains WebViewController tasks after frame rendering. macOS, Windows, and Linux native bridges enforce the shared WebViewSecurityPolicy before committing a navigation; blocked URLs produce a NavigationFailed event. Matching-host smoke is still required before promoting Linux WebView runtime observation beyond package-level compile coverage. The Linux host loop drains RendererEvent image requests, keeps only cancellable byte-I/O tasks, and returns token-matched completions to the selected session. Linux reads local files into HostImageSource bytes; the selected renderer owns decode, resource caches, and completion diagnostics. Applied completions request redraw for the matching Wayland window, while stale or disposed tokens are ignored. Package tests cover the host route from a loading first-frame request through completion, repaint request, and second presented frame. The required async second-frame runtime artifact remains matching-host pending until a Wayland run records it from a Skia composition root. Package tests do not prove a real Wayland compositor presented Showcase frames; those claims still require matching-host runtime runs and smoke logs manifest entries.

Runtime Evidence

X11 backend validation runs the window-module smoke natively on a Linux host or the UTM Ubuntu VM (scripts/linux-x11-vm-smoke.sh): the X11 forced leg, the auto-selection leg (DISPLAY-only), and the Wayland regression leg (headless Weston). Sentinel evidence lands in artifacts/linux-x11-vm/. Skia first-frame evidence below is recorded on the matching Wayland host; the X11 slice is CPU raster only.

For Linux Skia runtime evidence, record these as separate ignored artifacts/ logs on the matching Wayland host:

MOUI_FIRST_FRAME_EXIT=1 \\
  moon run examples/showcase/linux_skia --target native
scripts/run-window-package-smoke.sh linux --run

The Showcase log must include Linux renderer presented first frame; exiting by request; title=... from the host loop before they can be cited as app-level runtime evidence. The window package smoke remains dependency-level evidence for Wayland handles, present_rgba_pixels, resize/redraw, IME request state, and clean shutdown.

The window package carries a consumer-style Linux smoke for this dependency surface. On a matching Wayland host, run scripts/run-window-package-smoke.sh linux --run to exercise surface creation, public Wayland handles, Window::present_rgba_pixels, resize, redraw, IME request state, and clean shutdown. Add --require-input or WINDOW_MOUI_LINUX_REQUIRE_INPUT=1 only when representative pointer/keyboard input is observed. Linux clipboard selection, file dialogs, text-file reads and writes, desktop URL opening, IME composition/cursor geometry, and file drag/drop are implemented host-service/input paths, but they remain matching-host runtime evidence boundaries: cite only logs that exercised the actual desktop/compositor service, not the package preflight summary alone. Record dependency-level facts from the wzzc-dev/window@0.5.4-0.2.1 package smoke artifacts; keep the MoUI Showcase linux_skia run as the mainline application observation. Keep linux_wgpu as a WGPU diagnostic observation when a Vulkan/WGPU stack is configured.

Linux WebView runtime evidence belongs to a matching-host tester/backend probe. Package tests cover pure event/command mapping and fallback capability paths, but cannot prove that a real WebKitGTK view was presented.

examples/showcase/linux_skia selects the canonical Linux Skia provider route. Configure real Skia link flags before relying on native Skia-rendered pixels. The default JetBrains Linux provider links fontconfig, FreeType, and HarfBuzz; with those libraries available, moui_skia builds a system FontMgr through fontconfig and falls back to common font directories such as /usr/share/fonts when fontconfig reports no families. Missing CJK or emoji glyph coverage still depends on installed system fonts, and full mixed-script fallback runs remain a text-system follow-up rather than a Linux backend responsibility.

Linux native context menus use the shared HostServiceBridge::ShowMenu contract. The backend encodes enabled command rows for a desktop menu picker, dispatches the selected ActionCommand through HostRuntimeDriver, and reports an unavailable response when the configured desktop menu tool is absent.

Linux AT-SPI accessibility work stays behind backend/linux: the neutral binding and native GDBus transport publish committed snapshots/deltas, carry exact-generation actions back through the shared runtime entrypoint, and tear down window-scoped objects. Native accessibility readiness remains false until the transport registers on the AT-SPI accessibility bus/Registry and a real AT-SPI client proves tree queries, actions, focus, deletion, and announcements. Session-bus object export and package tests are not matching-host evidence.

Remaining Gaps

Remaining Linux gaps stay visible in backend/linux.readiness():

  • Linux clipboard, file-dialog, text-file, open URL, text-input/IME request, and file drag/drop host surfaces are implemented, but passed platform status still requires matching-host Wayland/desktop-service observation rather than package preflight alone.

WSL2 Verification Progress (2026-07-11)

On 2026-07-11, an end-to-end runtime evidence capture was completed on WSL2 + WSLg (Debian 13 on Windows):

bash window/scripts/capture_moui_runtime_evidence.sh linux \
  --log artifacts/platform-evidence/linux/moui-linux-runtime.log

Passed:

  • ✅ Wayland surface/handles/present/cursor/resize/redraw — all working correctly
  • ✅ IME probe: all 8 fields passed (enabled, hint, surrounding, cursor, updated, updated_hint, updated_cursor, disabled all true)
  • ✅ Clipboard data device: clipboard=true clipboard_roundtrip=true drag_drop=true
  • ✅ check_ci.sh CI check passed

Still requires a real Wayland desktop:

  • ❌ Interactive pointer/keyboard input (cannot be sent automatically in WSL2)
  • ❌ Complete destroy sequence (requires focused window interaction)

The IME protocol functionality has been verified via WSL2. Full L3 runtime pass requires running WINDOW_MOUI_LINUX_REQUIRE_INPUT=1 mode on a real Wayland desktop (Ubuntu 24.04+) with actual keyboard presses and mouse clicks.

MoUI Studio (linux_skia)

moui_studio/linux_skia is the Studio's Linux Skia entrypoint, following the momark template: @linux_backend.app_environment() + entry(smoke_options=...) + @render_skia.from_env(platform=Linux), wired to the same @studio_app.program composition root as the macOS and Web entries. The MOUI_FIRST_FRAME_EXIT=1 smoke convention is supported for headless evidence.

Capability parity: the provider worker (async @http), compile track (moon subprocess), project/workspace persistence (x/fs), full-text search, and MCP agent stdio transport are shared cross-platform. Explicitly absent on this platform (each degrades with a structured message, never a crash):

  • PTY terminal (macOS-only pty.c stub); the Teaching terminal chip is not rendered.
  • WebView live preview (macOS-only WKWebView); Frame preview remains available.
  • Wall-clock FFI (macOS-only); timestamps fall back to the constant clock.
  • Immersive titlebar and window drag (macOS-only window frame integration).

File dialogs (pick_directory) depend on the Linux service bridge's file_dialog capability; when the host does not provide one, import/export actions grey out with an explanatory notice.

Evidence: scripts/run-window-package-smoke.sh linux --run or MOUI_FIRST_FRAME_EXIT=1 moon run moui_studio/linux_skia --target native.